Files
GoldenCheetah/src/R/RTool.cpp
2025-07-11 16:06:48 -03:00

4250 lines
144 KiB
C++

/*
* Copyright (c) 2016 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 "RTool.h"
#include "RGraphicsDevice.h"
#include "GcUpgrade.h"
#include "RideCache.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "RideFile.h"
#include "RideFileCache.h"
#include "Colors.h"
#include "RideMetric.h"
#include "RideMetadata.h"
#include "PMCData.h"
#include "WPrime.h"
#include "Season.h"
#include "Seasons.h"
#include "DataFilter.h"
#include "Specification.h"
#include "Zones.h"
#include "HrZones.h"
#include "PaceZones.h"
#include "GenericChart.h"
#include "Perspective.h"
#include "SpecialFields.h"
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wcast-function-type" // shut gcc up
#endif
// Structure used to register routines has changed in v3.4 of R
//
// there is no way to support older versions without declaring our
// own versions of these structures. The R code maintainers don't
// consider legacy support, so we have to do this hack
//
// reduced number of members from v3.4 onwards
typedef struct {
const char *name;
DL_FUNC fun;
int numArgs;
R_NativePrimitiveArgType *types;
} R_CMethodDef34;
// prior to v3.4 they used this structure
typedef struct {
const char *name;
DL_FUNC fun;
int numArgs;
R_NativePrimitiveArgType *types;
enum { R_ARG_IN, R_ARG_OUT, R_IRRELEVANT } *styles; // deprecated in 3.4
} R_CMethodDef33;
RTool::RTool()
{
// setup the R runtime elements
failed = false;
starting = true;
canvas = NULL;
perspective = NULL;
chart = NULL;
context = NULL;
// if we bail we need to explain why, its in here
QString dialogtext;
try {
// yikes, self referenced during construction (!)
rtool = this;
// set default width and height
width = height = 500;
// initialise
R = new REmbed();
// failed to load
if (R->loaded == false) {
failed=true;
return;
}
// capture all output and input to our methods
#ifndef WIN32
ptr_R_Suicide = &RTool::R_Suicide;
ptr_R_ShowMessage = &RTool::R_ShowMessage;
ptr_R_ReadConsole = &RTool::R_ReadConsole;
ptr_R_WriteConsole = &RTool::R_WriteConsole;
ptr_R_WriteConsoleEx = &RTool::R_WriteConsoleEx;
ptr_R_ResetConsole = &RTool::R_ResetConsole;
ptr_R_FlushConsole = &RTool::R_FlushConsole;
ptr_R_ClearerrConsole = &RTool::R_ClearerrConsole;
ptr_R_ProcessEvents = &RTool::R_ProcessEvents;
ptr_R_Busy = &RTool::R_Busy;
// turn off stderr io
R_Outputfile = NULL;
R_Consolefile = NULL;
#endif
// IMPORTANT: **** REMEMBER TO CHANGE BOTH WHEN ADDING NEW ROUTINES ****
//
// setup when embedding v3.4 or higher
R_CMethodDef34 cMethods34[] = {
{ "GC.display", (DL_FUNC) &RGraphicsDevice::GCdisplay, 0,0 },
{ "GC.page", (DL_FUNC) &RTool::pageSize, 0,0 },
{ "GC.size", (DL_FUNC) &RTool::windowSize, 0,0 },
{ "GC.athlete", (DL_FUNC) &RTool::athlete, 0,0 },
{ "GC.athlete.zones", (DL_FUNC) &RTool::zones, 0,0 },
{ "GC.intervalType", (DL_FUNC) &RTool::intervalType, 0,0 },
{ "GC.activities", (DL_FUNC) &RTool::activities, 0,0 },
{ "GC.activity", (DL_FUNC) &RTool::activity, 0,0 },
{ "GC.activity.metrics", (DL_FUNC) &RTool::activityMetrics, 0,0 },
{ "GC.activity.meanmax", (DL_FUNC) &RTool::activityMeanmax, 0,0 },
{ "GC.activity.wbal", (DL_FUNC) &RTool::activityWBal, 0,0 },
{ "GC.activity.xdata", (DL_FUNC) &RTool::activityXData, 0,0 },
{ "GC.activity.intervals", (DL_FUNC) &RTool::activityIntervals, 0,0 },
{ "GC.season", (DL_FUNC) &RTool::season, 0,0 },
{ "GC.season.metrics", (DL_FUNC) &RTool::metrics, 0,0 },
{ "GC.season.intervals", (DL_FUNC) &RTool::seasonIntervals, 0,0 },
{ "GC.season.pmc", (DL_FUNC) &RTool::pmc, 0,0 },
{ "GC.season.meanmax", (DL_FUNC) &RTool::seasonMeanmax, 0,0 },
{ "GC.season.peaks", (DL_FUNC) &RTool::seasonPeaks, 0,0 },
{ "GC.season.measures", (DL_FUNC) &RTool::measures, 0,0 },
{ "GC.chart.set", (DL_FUNC) &RTool::setChart, 0,0 },
{ "GC.chart.addCurve", (DL_FUNC) &RTool::addCurve, 0,0 },
{ "GC.chart.configureAxis", (DL_FUNC) &RTool::configureAxis, 0,0 },
{ "GC.chart.annotate", (DL_FUNC) &RTool::annotate, 0,0 },
{ NULL, NULL, 0,0 }
};
// setup when embedding prior to 3.4
R_CMethodDef33 cMethods33[] = {
{ "GC.display", (DL_FUNC) &RGraphicsDevice::GCdisplay, 0,0,0 },
{ "GC.page", (DL_FUNC) &RTool::pageSize, 0,0,0 },
{ "GC.size", (DL_FUNC) &RTool::windowSize, 0,0,0 },
{ "GC.athlete", (DL_FUNC) &RTool::athlete, 0,0,0 },
{ "GC.athlete.zones", (DL_FUNC) &RTool::zones, 0,0,0 },
{ "GC.intervalType", (DL_FUNC) &RTool::intervalType, 0,0,0 },
{ "GC.activities", (DL_FUNC) &RTool::activities, 0,0,0 },
{ "GC.activity", (DL_FUNC) &RTool::activity, 0,0,0 },
{ "GC.activity.metrics", (DL_FUNC) &RTool::activityMetrics, 0,0,0 },
{ "GC.activity.meanmax", (DL_FUNC) &RTool::activityMeanmax, 0,0,0 },
{ "GC.activity.wbal", (DL_FUNC) &RTool::activityWBal, 0,0,0 },
{ "GC.activity.xdata", (DL_FUNC) &RTool::activityXData, 0,0,0 },
{ "GC.activity.intervals", (DL_FUNC) &RTool::activityIntervals, 0,0,0 },
{ "GC.season", (DL_FUNC) &RTool::season, 0,0,0 },
{ "GC.season.metrics", (DL_FUNC) &RTool::metrics, 0,0,0 },
{ "GC.season.intervals", (DL_FUNC) &RTool::seasonIntervals, 0,0,0 },
{ "GC.season.pmc", (DL_FUNC) &RTool::pmc, 0,0,0 },
{ "GC.season.meanmax", (DL_FUNC) &RTool::seasonMeanmax, 0,0,0 },
{ "GC.season.peaks", (DL_FUNC) &RTool::seasonPeaks, 0,0,0 },
{ "GC.season.measures", (DL_FUNC) &RTool::measures, 0,0,0 },
{ "GC.chart.set", (DL_FUNC) &RTool::setChart, 0,0,0 },
{ "GC.chart.addCurve", (DL_FUNC) &RTool::addCurve, 0,0,0 },
{ "GC.chart.configureAxis", (DL_FUNC) &RTool::configureAxis, 0,0,0 },
{ "GC.chart.annotate", (DL_FUNC) &RTool::annotate, 0,0,0 },
{ NULL, NULL, 0,0,0 }
};
R_CallMethodDef callMethods[] = {
{ "GC.display", (DL_FUNC) &RGraphicsDevice::GCdisplay, 0 },
{ "GC.page", (DL_FUNC) &RTool::pageSize, 2 },
{ "GC.size", (DL_FUNC) &RTool::windowSize, 0 },
// athlete
{ "GC.athlete", (DL_FUNC) &RTool::athlete, 0 },
{ "GC.athlete.zones", (DL_FUNC) &RTool::zones, 2 },
// intervals
{ "GC.intervalType", (DL_FUNC) &RTool::intervalType, 1 },
// if activity is passed compare=TRUE it returns a list of rides
// currently in the compare pane if compare is enabled or
// just a 1 item list with the current ride
{ "GC.activities", (DL_FUNC) &RTool::activities, 1 },
{ "GC.activity", (DL_FUNC) &RTool::activity, 4 },
{ "GC.activity.metrics", (DL_FUNC) &RTool::activityMetrics, 1 },
{ "GC.activity.meanmax", (DL_FUNC) &RTool::activityMeanmax, 1 },
{ "GC.activity.wbal", (DL_FUNC) &RTool::activityWBal, 1 },
// name="", compare=FALSE
{ "GC.activity.xdata", (DL_FUNC) &RTool::activityXData, 2 },
// type=any, datetime=0
{ "GC.activity.intervals", (DL_FUNC) &RTool::activityIntervals, 2 },
// all=FALSE, compare=FALSE
{ "GC.season", (DL_FUNC) &RTool::season, 2 },
{ "GC.season.metrics", (DL_FUNC) &RTool::metrics, 3 },
// type=any, compare=FALSE
{ "GC.season.intervals", (DL_FUNC) &RTool::seasonIntervals, 2 },
{ "GC.season.meanmax", (DL_FUNC) &RTool::seasonMeanmax, 3 },
{ "GC.season.peaks", (DL_FUNC) &RTool::seasonPeaks, 5 },
// return a data.frame of pmc series (all=FALSE, metric="BikeStress", type="Actual")
{ "GC.season.pmc", (DL_FUNC) &RTool::pmc, 3 },
// return a data.frame of measure fields (all=FALSE, group="Body")
{ "GC.season.measures", (DL_FUNC) &RTool::measures, 2 },
{ "GC.chart.set", (DL_FUNC) &RTool::setChart, 6 },
{ "GC.chart.addCurve", (DL_FUNC) &RTool::addCurve, 17 },
{ "GC.chart.configureAxis", (DL_FUNC) &RTool::configureAxis, 10 },
{ "GC.chart.annotate", (DL_FUNC) &RTool::annotate, 4 },
{ NULL, NULL, 0 }
};
// dynamically loading now, so the version may not be
// the same as the version we built with.
// get version just loaded from shared lib
R->parseEvalNT("print(R.version.string)");
QStringList strings = rtool->messages;
if (strings.count() == 3) {
QRegExp exp("^.*([0-9]+\\.[0-9]+\\.[0-9]+).*$");
if (exp.exactMatch(strings[1])) version = exp.cap(1);
else version = strings[1];
}
QStringList nums = version.split(".");
int majorN = nums[0].toInt();
int minorN = nums[1].toInt();
// now check version compatibility against what we built with
double majorB=QString(R_MAJOR).toDouble();
double minorB=QString(R_MINOR).toDouble();
if (majorB > 3 || (majorB == 3 && minorB > 3)) {
// if we're built with 3.4 or higher we need 3.4 or higher
if (majorN < 3 || (majorN == 3 && minorN < 4)) {
// we have an older version so no go
failed=true;
dialogtext = QObject::tr("The version of R installed is too old. You must have R version 3.4 or higher.\n");
goto fail;
}
} else {
// if we're build with <3.4 don't support 3.4 or higher
if (majorN >3 || (majorN == 3 && minorN > 3)) {
// we have an older version so no go
failed=true;
dialogtext = QObject::tr("The version of R installed is too new. You must have R version 3.3 or older.\n");
goto fail;
}
}
// should be safe to setup the graphics device now
dev = new RGraphicsDevice();
// set them up
DllInfo *info = R_getEmbeddingDllInfo();
// future proof, 3.4 or higher use new structure, 3.3 anything lower uses older structure
if (majorN > 3 || (majorN == 3 && minorN > 3)) R_registerRoutines(info, (const R_CMethodDef*)(cMethods34), callMethods, NULL, NULL);
else R_registerRoutines(info, (const R_CMethodDef*)(cMethods33), callMethods, NULL, NULL);
// what version are we running?
#ifdef GC_WANT_ALLDEBUG
fprintf(stderr,"R loaded. [Compiled=%s.%s, Loaded=%d.%d, Loaded DeviceEngine=%d]\n", R_MAJOR, R_MINOR, majorN, minorN, GC_R_GE_getVersion());
#endif
rtool->messages.clear();
// load the dynamix library and create function wrapper
// we should put this into a source file (.R)
R->parseEvalNT(QString("options(\"repos\"=\"%3\")\n"
// graphics device
"GC.display <- function() { .Call(\"GC.display\") }\n"
"GC.page <- function(width=0, height=0) { .Call(\"GC.page\", width, height) }\n"
"GC.size <- function() { .Call(\"GC.size\") }\n"
// athlete
"GC.athlete <- function() { .Call(\"GC.athlete\") }\n"
"GC.athlete.zones <- function(date=0, sport=\"\") { .Call(\"GC.athlete.zones\", date, sport) }\n"
// intervals
"GC.intervalType <- function(type=1) { .Call(\"GC.intervalType\", type) }\n"
// activity
"GC.activities <- function(filter=\"\") { .Call(\"GC.activities\", filter) }\n"
"GC.activity <- function(activity=0, compare=FALSE, split=0, join=\"repeat\") { .Call(\"GC.activity\", activity, compare, split, join) }\n"
"GC.activity.metrics <- function(compare=FALSE) { .Call(\"GC.activity.metrics\", compare) }\n"
"GC.activity.meanmax <- function(compare=FALSE) { .Call(\"GC.activity.meanmax\", compare) }\n"
"GC.activity.wbal <- function(compare=FALSE) { .Call(\"GC.activity.wbal\", compare) }\n"
"GC.activity.xdata <- function(name=\"\", compare=FALSE) { .Call(\"GC.activity.xdata\", name, compare) }\n"
"GC.activity.intervals <- function(type=NULL, activity=0) { .Call(\"GC.activity.intervals\", type, activity) }\n"
// season
"GC.season <- function(all=FALSE, compare=FALSE) { .Call(\"GC.season\", all, compare) }\n"
"GC.season.metrics <- function(all=FALSE, filter=\"\", compare=FALSE) { .Call(\"GC.season.metrics\", all, filter, compare) }\n"
"GC.season.intervals <- function(type=NULL, compare=FALSE) { .Call(\"GC.season.intervals\", type, compare) }\n"
"GC.season.pmc <- function(all=FALSE, metric=\"BikeStress\", type=\"Actual\") { .Call(\"GC.season.pmc\", all, metric, type) }\n"
"GC.season.measures <- function(all=FALSE, group=\"Body\") { .Call(\"GC.season.measures\", all, group) }\n"
"GC.season.meanmax <- function(all=FALSE, filter=\"\", compare=FALSE) { .Call(\"GC.season.meanmax\", all, filter, compare) }\n"
// find peaks does a few validation checks on the R side
"GC.season.peaks <- function(all=FALSE, filter=\"\", compare=FALSE, series, duration) {\n"
" if (missing(series)) stop(\"series must be specified.\")\n"
" if (missing(duration)) stop(\"duration must be specified.\")\n"
" if (!is.numeric(duration)) stop(\"duration must be numeric.\")\n"
" .Call(\"GC.season.peaks\", all, filter, compare, series, duration)"
"}\n"
// these 2 added for backward compatibility, may be deprecated
"GC.metrics <- function(all=FALSE, filter=\"\", compare=FALSE) { .Call(\"GC.season.metrics\", all, filter, compare) }\n"
"GC.pmc <- function(all=FALSE, metric=\"BikeStress\", type=\"Actual\") { .Call(\"GC.season.pmc\", all, metric, type) }\n"
// charts
"GC.setChart <- function(title=\"\", type=1, animate=FALSE, legpos=2, stack=FALSE, orientation=2) { .Call(\"GC.chart.set\", title, type, animate, legpos ,stack, orientation)}\n"
"GC.addCurve <- function(name=\"curve\", xseries=c(), yseries=c(), fseries=c(), xname=\"xaxis\", yname=\"yaxis\", min=-1, max=-1, labels=c(), colors=c(), line=1,symbol=1,size=2,color=\"red\",opacity=100,opengl=TRUE, legend=TRUE, datalabels=FALSE, fill=FALSE) { .Call(\"GC.chart.addCurve\", name, xseries, yseries, fseries, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl, legend, datalabels, fill)}\n"
"GC.setAxis <- function(name=\"xaxis\",visible=TRUE, align=-1, min=-1, max=-1, type=0, labelcolor=\"\", color=\"\", log=FALSE, categories=c()) { .Call(\"GC.chart.configureAxis\", name, visible, align, min, max, type, labelcolor,color,log,categories)}\n"
"GC.annotate <- function(type=\"label\", series=\"curve\", strings=c()) { .Call(\"GC.chart.annotate\", type, series, strings, FALSE)}\n"
// constants
"GC.HORIZONTAL<-1\n"
"GC.VERTICAL<-2\n"
"GC.ALIGN.TOP<-2\n"
"GC.ALIGN.BOTTOM<-0\n"
"GC.ALIGN.LEFT<-1\n"
"GC.ALIGN.RIGHT<-3\n"
"GC.ALIGN.NONE<-4\n"
"GC.LINE.NONE<-0\n"
"GC.LINE.SOLID<-1\n"
"GC.LINE.DASH<-2\n"
"GC.LINE.DOT<-3\n"
"GC.LINE.DASHDOT<-4\n"
"GC.SYMBOL.NONE<-0\n"
"GC.SYMBOL.CIRCLE<-1\n"
"GC.SYMBOL.RECTANGLE<-2\n"
"GC.CHART.LINE<-1\n"
"GC.CHART.SCATTER<-2\n"
"GC.CHART.BAR<-3\n"
"GC.CHART.PIE<-4\n"
"GC.AXIS.CONTINUOUS<-0\n"
"GC.AXIS.DATE<-1\n"
"GC.AXIS.TIME<-2\n"
"GC.AXIS.CATEGORY<-3\n"
// version and build
"GC.version <- function() { return(\"%1\") }\n"
"GC.build <- function() { return(%2) }\n"
"par.default <- par()\n"
)
.arg(VERSION_STRING)
.arg(VERSION_LATEST)
.arg("https://cloud.r-project.org/"));
rtool->messages.clear();
configChanged();
} catch(std::exception& ex) {
qDebug()<<"Parse error:" << ex.what();
failed = true;
} catch(...) {
failed = true;
}
fail:
if (failed) {
// ack, disable R runtime now and in the future
qDebug() << "R Embed failed to start, RConsole disabled.";
appsettings->setValue(GC_EMBED_R, false);
version = "none";
R = NULL;
// end embedding
// Don't bug the user, most of them don't care
//QMessageBox warn(QMessageBox::Information, QObject::tr("R version Incompatible"),
// dialogtext + QObject::tr("\nR has been disabled in preferences"));
//warn.exec();
}
starting = false;
}
void
RTool::R_ProcessEvents()
{
QApplication::processEvents();
}
void
RTool::cancel()
{
// gets called when we need to stop a long running script
rtool->cancelled = true;
#ifdef WIN32
UserBreak = true;
#else
R_interrupts_pending = 1;
#endif
}
void
RTool::configChanged()
{
// wait until loaded
if (starting || failed) return;
// update global R appearances
QString parCommand=QString("par(par.default)\n"
"par(bg=\"%1\", "
" col=\"%2\", "
" fg=\"%2\", "
" col.main=\"%2\", "
" col.sub=\"%3\", "
" col.lab=\"%3\", "
" col.axis=\"%3\")\n"
"par.gc <- par()\n"
).arg(GColor(CPLOTBACKGROUND).name())
.arg(GCColor::invertColor(GColor(CPLOTBACKGROUND)).name())
.arg(GColor(CPLOTMARKER).name());
// fire and forget, don't care if it fails or not !!
rtool->R->parseEvalNT(parCommand);
}
SEXP
RTool::athlete()
{
if (rtool == NULL || rtool->context == NULL) return Rf_allocVector(INTSXP, 0);
// name, home, dob, height, weight, gender
SEXP ans, names;
PROTECT(ans=Rf_allocVector(VECSXP, 6));
PROTECT(names=Rf_allocVector(STRSXP, 6));
// next and nextS
SEXP item;
int next=0;
// NAME
PROTECT(item=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(item, 0, Rf_mkChar(rtool->context->athlete->cyclist.toLatin1().constData()));
SET_VECTOR_ELT(ans, next, item);
SET_STRING_ELT(names, next++, Rf_mkChar("name"));
UNPROTECT(1);
// HOME
PROTECT(item=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(item, 0, Rf_mkChar(rtool->context->athlete->home->root().absolutePath().toLatin1().constData()));
SET_VECTOR_ELT(ans, next, item);
SET_STRING_ELT(names, next++, Rf_mkChar("home"));
UNPROTECT(1);
// DOB
PROTECT(item=Rf_allocVector(INTSXP, 1));
QDate d1970(1970,01,01);
INTEGER(item)[0] = d1970.daysTo(appsettings->cvalue(rtool->context->athlete->cyclist, GC_DOB).toDate());
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(item,dclas);
SET_VECTOR_ELT(ans, next, item);
SET_STRING_ELT(names, next++, Rf_mkChar("dob"));
UNPROTECT(2);
// WEIGHT
PROTECT(item=Rf_allocVector(REALSXP, 1));
REAL(item)[0] = appsettings->cvalue(rtool->context->athlete->cyclist, GC_WEIGHT).toDouble();
SET_VECTOR_ELT(ans, next, item);
SET_STRING_ELT(names, next++, Rf_mkChar("weight"));
UNPROTECT(1);
// HEIGHT
PROTECT(item=Rf_allocVector(REALSXP, 1));
REAL(item)[0] = appsettings->cvalue(rtool->context->athlete->cyclist, GC_HEIGHT).toDouble();
SET_VECTOR_ELT(ans, next, item);
SET_STRING_ELT(names, next++, Rf_mkChar("height"));
UNPROTECT(1);
// GENDER
int isfemale = appsettings->cvalue(rtool->context->athlete->cyclist, GC_SEX).toInt();
PROTECT(item=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(item, 0, isfemale ? Rf_mkChar("female") : Rf_mkChar("male"));
SET_VECTOR_ELT(ans, next, item);
SET_STRING_ELT(names, next++, Rf_mkChar("gender"));
UNPROTECT(1);
// set the names
Rf_namesgets(ans, names);
// ans + names
UNPROTECT(2);
return ans;
}
// one entry per sport per date for hr/power/pace
class gcZoneConfig {
public:
gcZoneConfig(QString sport) : sport(sport), date(QDate(01,01,01)), cp(0), wprime(0), pmax(0), aetp(0), ftp(0),lthr(0),aethr(0),rhr(0),hrmax(0),cv(0),aetv(0) {}
bool operator<(gcZoneConfig rhs) const { return date < rhs.date; }
QString sport;
QDate date;
QList<int> zoneslow;
QList<int> hrzoneslow;
QList<double> pacezoneslow;
int cp, wprime, pmax,aetp,ftp,lthr,aethr,rhr,hrmax;
double cv, aetv;
};
SEXP
RTool::zones(SEXP pDate, SEXP pSport)
{
// return a dataframe with
// date, sport, cp, w', pmax, aetp, ftp, lthr, aethr, rhr, hrmax, cv, aetv, zoneslow, hrzoneslow, pacezoneslow, zonescolor
// need non-null context
if (!rtool || !rtool->context) return Rf_allocVector(INTSXP, 0);
// COLLECT ALL THE CONFIG TOGETHER
QList<gcZoneConfig> config;
// for a specific date?
QDate d1970(1970,01,01);
PROTECT(pDate=Rf_coerceVector(pDate,INTSXP));
if (INTEGER(pDate)[0] != 0) {
// get settings for...
QDate forDate=d1970.addDays(INTEGER(pDate)[0]);
// Look for the range in Power, HR and Pace zones for each sport
foreach (QString sp, GlobalContext::context()->rideMetadata->sports()) {
// Power Zones
if (rtool->context->athlete->zones(sp)) {
// run through the power zones
int range=rtool->context->athlete->zones(sp)->whichRange(forDate);
if (range >= 0) {
gcZoneConfig c(sp);
c.date = forDate;
c.cp = rtool->context->athlete->zones(sp)->getCP(range);
c.wprime = rtool->context->athlete->zones(sp)->getWprime(range);
c.pmax = rtool->context->athlete->zones(sp)->getPmax(range);
c.aetp = rtool->context->athlete->zones(sp)->getAeT(range);
c.ftp = rtool->context->athlete->zones(sp)->getFTP(range);
c.zoneslow = rtool->context->athlete->zones(sp)->getZoneLows(range);
config << c;
}
}
// HR Zones
if (rtool->context->athlete->hrZones(sp)) {
int range=rtool->context->athlete->hrZones(sp)->whichRange(forDate);
if (range >= 0) {
gcZoneConfig c(sp);
c.date = forDate;
c.lthr = rtool->context->athlete->hrZones(sp)->getLT(range);
c.aethr = rtool->context->athlete->hrZones(sp)->getAeT(range);
c.rhr = rtool->context->athlete->hrZones(sp)->getRestHr(range);
c.hrmax = rtool->context->athlete->hrZones(sp)->getMaxHr(range);
c.hrzoneslow = rtool->context->athlete->hrZones(sp)->getZoneLows(range);
config << c;
}
}
// Pace Zones
if ((sp == "Run" || sp == "Swim") && rtool->context->athlete->paceZones(sp=="Swim")) {
int range=rtool->context->athlete->paceZones(sp=="Swim")->whichRange(forDate);
if (range >= 0) {
gcZoneConfig c(sp);
c.date = forDate;
c.cv = rtool->context->athlete->paceZones(sp=="Swim")->getCV(range);
c.aetv = rtool->context->athlete->paceZones(sp=="Swim")->getAeT(range);
c.pacezoneslow = rtool->context->athlete->paceZones(sp=="Swim")->getZoneLows(range);
config << c;
}
}
}
} else {
// Look for the ranges in Power, HR and Pace zones for each sport
foreach (QString sp, GlobalContext::context()->rideMetadata->sports()) {
// Power Zones
if (rtool->context->athlete->zones(sp)) {
for (int range=0; range < rtool->context->athlete->zones(sp)->getRangeSize(); range++) {
// run through the power zones
gcZoneConfig c(sp);
c.date = rtool->context->athlete->zones(sp)->getStartDate(range);
c.cp = rtool->context->athlete->zones(sp)->getCP(range);
c.wprime = rtool->context->athlete->zones(sp)->getWprime(range);
c.pmax = rtool->context->athlete->zones(sp)->getPmax(range);
c.aetp = rtool->context->athlete->zones(sp)->getAeT(range);
c.ftp = rtool->context->athlete->zones(sp)->getFTP(range);
c.zoneslow = rtool->context->athlete->zones(sp)->getZoneLows(range);
config << c;
}
}
// HR Zones
if (rtool->context->athlete->hrZones(sp)) {
for (int range=0; range < rtool->context->athlete->hrZones(sp)->getRangeSize(); range++) {
gcZoneConfig c(sp);
c.date = rtool->context->athlete->hrZones(sp)->getStartDate(range);
c.lthr = rtool->context->athlete->hrZones(sp)->getLT(range);
c.aethr = rtool->context->athlete->hrZones(sp)->getAeT(range);
c.rhr = rtool->context->athlete->hrZones(sp)->getRestHr(range);
c.hrmax = rtool->context->athlete->hrZones(sp)->getMaxHr(range);
c.hrzoneslow = rtool->context->athlete->hrZones(sp)->getZoneLows(range);
config << c;
}
}
// Pace Zones
if ((sp == "Run" || sp == "Swim") && rtool->context->athlete->paceZones(sp=="Swim")) {
for (int range=0; range < rtool->context->athlete->paceZones(sp=="Swim")->getRangeSize(); range++) {
gcZoneConfig c(sp);
c.date = rtool->context->athlete->paceZones(sp=="Swim")->getStartDate(range);
c.cv = rtool->context->athlete->paceZones(sp=="Swim")->getCV(range);
c.aetv = rtool->context->athlete->paceZones(sp=="Swim")->getAeT(range);
c.pacezoneslow = rtool->context->athlete->paceZones(sp=="Swim")->getZoneLows(range);
config << c;
}
}
}
}
// pDate
UNPROTECT(1);
// no config ?
if (config.count() == 0) return Rf_allocVector(INTSXP, 0);
// COMPRESS CONFIG TOGETHER BY SPORT
// filter sport?
PROTECT(pSport=Rf_coerceVector(pSport, STRSXP));
QString want(CHAR(STRING_ELT(pSport,0)));
UNPROTECT(1);
// compress here
QList<gcZoneConfig> compressed;
std::sort(config.begin(),config.end());
foreach (QString sp, GlobalContext::context()->rideMetadata->sports()) {
// will have date zero
gcZoneConfig last(sp);
foreach(gcZoneConfig x, config) {
if (x.sport == sp && (want=="" || QString::compare(want, sp, Qt::CaseInsensitive)==0)) {
// new date so save what we have collected
if (x.date > last.date) {
if (last.date > QDate(01,01,01)) compressed << last;
last.date = x.date;
}
// merge new values
if (x.date == last.date) {
// merge with prior
if (x.cp) last.cp = x.cp;
if (x.wprime) last.wprime = x.wprime;
if (x.pmax) last.pmax = x.pmax;
if (x.aetp) last.aetp = x.aetp;
if (x.ftp) last.ftp = x.ftp;
if (x.lthr) last.lthr = x.lthr;
if (x.aethr) last.aethr = x.aethr;
if (x.rhr) last.rhr = x.rhr;
if (x.hrmax) last.hrmax = x.hrmax;
if (x.cv) last.cv = x.cv;
if (x.aetv) last.aetv = x.aetv;
if (x.zoneslow.length()) last.zoneslow = x.zoneslow;
if (x.hrzoneslow.length()) last.hrzoneslow = x.hrzoneslow;
if (x.pacezoneslow.length()) last.pacezoneslow = x.pacezoneslow;
}
}
}
if (last.date > QDate(01,01,01)) compressed << last;
}
// now use the new compressed ones
config = compressed;
std::sort(config.begin(),config.end());
int size = config.count();
// CREATE A DATAFRAME OF CONFIG
SEXP ans;
PROTECT(ans = Rf_allocVector(VECSXP, 17));
// 17 columns, size rows
SEXP date;
SEXP sport;
SEXP cp, wprime, pmax,aetp,ftp,lthr,aethr,rhr,hrmax,cv,aetv, zoneslow, hrzoneslow, pacezoneslow, zonescolor;
SEXP rownames;
PROTECT(date=Rf_allocVector(INTSXP, size));
PROTECT(sport=Rf_allocVector(STRSXP, size));
PROTECT(cp=Rf_allocVector(INTSXP, size));
PROTECT(wprime=Rf_allocVector(INTSXP, size));
PROTECT(pmax=Rf_allocVector(INTSXP, size));
PROTECT(aetp=Rf_allocVector(INTSXP, size));
PROTECT(ftp=Rf_allocVector(INTSXP, size));
PROTECT(lthr=Rf_allocVector(INTSXP, size));
PROTECT(aethr=Rf_allocVector(INTSXP, size));
PROTECT(rhr=Rf_allocVector(INTSXP, size));
PROTECT(hrmax=Rf_allocVector(INTSXP, size));
PROTECT(cv=Rf_allocVector(REALSXP, size));
PROTECT(aetv=Rf_allocVector(REALSXP, size));
PROTECT(zoneslow=Rf_allocVector(VECSXP, size));
PROTECT(hrzoneslow=Rf_allocVector(VECSXP, size));
PROTECT(pacezoneslow=Rf_allocVector(VECSXP, size));
PROTECT(zonescolor=Rf_allocVector(VECSXP, size));
PROTECT(rownames=Rf_allocVector(STRSXP, size));
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(date,dclas);
int index=0;
foreach(gcZoneConfig x, config) {
// update the arrays
INTEGER(date)[index] = d1970.daysTo(x.date);
SET_STRING_ELT(sport, index, Rf_mkChar(x.sport.toLatin1().constData()));
SET_STRING_ELT(rownames, index, Rf_mkChar(QString("%1").arg(index+1).toLatin1().constData()));
INTEGER(cp)[index] = x.cp;
INTEGER(wprime)[index] = x.wprime;
INTEGER(pmax)[index] = x.pmax;
INTEGER(aetp)[index] = x.aetp;
INTEGER(ftp)[index] = x.ftp;
INTEGER(lthr)[index] = x.lthr;
INTEGER(aethr)[index] = x.aethr;
INTEGER(rhr)[index] = x.rhr;
INTEGER(hrmax)[index] = x.hrmax;
REAL(cv)[index] = x.cv;
REAL(aetv)[index] = x.aetv;
SEXP lows, hrlows, pacelows, colors;
PROTECT(lows=Rf_allocVector(INTSXP, x.zoneslow.length()));
PROTECT(hrlows=Rf_allocVector(INTSXP, x.hrzoneslow.length()));
PROTECT(pacelows=Rf_allocVector(REALSXP, x.pacezoneslow.length()));
PROTECT(colors=Rf_allocVector(STRSXP, x.zoneslow.length()));
int indexlow=0;
foreach(int low, x.zoneslow) {
INTEGER(lows)[indexlow] = low;
SET_STRING_ELT(colors, indexlow, Rf_mkChar(zoneColor(indexlow, x.zoneslow.length()).name().toLatin1().constData()));
indexlow++;
}
indexlow=0;
foreach(int low, x.hrzoneslow) {
INTEGER(hrlows)[indexlow] = low;
indexlow++;
}
indexlow=0;
foreach(double low, x.pacezoneslow) {
REAL(pacelows)[indexlow] = low;
indexlow++;
}
SET_VECTOR_ELT(zoneslow, index, lows);
SET_VECTOR_ELT(hrzoneslow, index, hrlows);
SET_VECTOR_ELT(pacezoneslow, index, pacelows);
SET_VECTOR_ELT(zonescolor, index, colors);
UNPROTECT(4);
index++;
}
// add to frame
SET_VECTOR_ELT(ans, 0, date);
SET_VECTOR_ELT(ans, 1, sport);
SET_VECTOR_ELT(ans, 2, cp);
SET_VECTOR_ELT(ans, 3, wprime);
SET_VECTOR_ELT(ans, 4, pmax);
SET_VECTOR_ELT(ans, 5, aetp);
SET_VECTOR_ELT(ans, 6, ftp);
SET_VECTOR_ELT(ans, 7, lthr);
SET_VECTOR_ELT(ans, 8, aethr);
SET_VECTOR_ELT(ans, 9, rhr);
SET_VECTOR_ELT(ans, 10, hrmax);
SET_VECTOR_ELT(ans, 11, cv);
SET_VECTOR_ELT(ans, 12, aetv);
SET_VECTOR_ELT(ans, 13, zoneslow);
SET_VECTOR_ELT(ans, 14, hrzoneslow);
SET_VECTOR_ELT(ans, 15, pacezoneslow);
SET_VECTOR_ELT(ans, 16, zonescolor);
// turn into a data.frame, name class etc
SEXP names;
PROTECT(names = Rf_allocVector(STRSXP, 17));
SET_STRING_ELT(names, 0, Rf_mkChar("date"));
SET_STRING_ELT(names, 1, Rf_mkChar("sport"));
SET_STRING_ELT(names, 2, Rf_mkChar("cp"));
SET_STRING_ELT(names, 3, Rf_mkChar("wprime"));
SET_STRING_ELT(names, 4, Rf_mkChar("pmax"));
SET_STRING_ELT(names, 5, Rf_mkChar("aetp"));
SET_STRING_ELT(names, 6, Rf_mkChar("ftp"));
SET_STRING_ELT(names, 7, Rf_mkChar("lthr"));
SET_STRING_ELT(names, 8, Rf_mkChar("aethr"));
SET_STRING_ELT(names, 9, Rf_mkChar("rhr"));
SET_STRING_ELT(names, 10, Rf_mkChar("hrmax"));
SET_STRING_ELT(names, 11, Rf_mkChar("cv"));
SET_STRING_ELT(names, 12, Rf_mkChar("aetv"));
SET_STRING_ELT(names, 13, Rf_mkChar("zoneslow"));
SET_STRING_ELT(names, 14, Rf_mkChar("hrzoneslow"));
SET_STRING_ELT(names, 15, Rf_mkChar("pacezoneslow"));
SET_STRING_ELT(names, 16, Rf_mkChar("zonescolor"));
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
UNPROTECT(21);
// fail
return ans;
}
SEXP
RTool::pageSize(SEXP width, SEXP height)
{
width = Rf_coerceVector(width, INTSXP);
rtool->width = INTEGER(width)[0];
height = Rf_coerceVector(height, INTSXP);
rtool->height = INTEGER(height)[0];
// fail
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::windowSize()
{
// return a vector of width, height
SEXP ans;
PROTECT(ans = Rf_allocVector(INTSXP, 2));
INTEGER(ans)[0] = rtool->chart ? rtool->chart->geometry().width() : 500;
INTEGER(ans)[1] = rtool->chart ? rtool->chart->geometry().height() : 500;
UNPROTECT(1);
return ans;
}
SEXP
RTool::activities(SEXP filter)
{
SEXP dates=NULL;
SEXP clas;
if (rtool->context && rtool->context->athlete && rtool->context->athlete->rideCache) {
// filters
// apply any global filters
Specification specification;
FilterSet fs;
fs.addFilter(rtool->context->isfiltered, rtool->context->filters);
fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters);
// did call contain any filters?
PROTECT(filter=Rf_coerceVector(filter, STRSXP));
for(int i=0; i<Rf_length(filter); i++) {
// if not empty write a filter
QString f(CHAR(STRING_ELT(filter,i)));
if (f != "") {
DataFilter dataFilter(rtool->canvas, rtool->context);
QStringList files;
dataFilter.parseFilter(rtool->context, f, &files);
fs.addFilter(true, files);
}
}
specification.setFilterSet(fs);
UNPROTECT(1);
// how many pass?
int count=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
// apply filters
if (!specification.pass(item)) continue;
count++;
}
// allocate space for a vector of dates
PROTECT(dates=Rf_allocVector(REALSXP, count));
// fill with values for date and class
int i=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
// apply filters
if (!specification.pass(item)) continue;
// add to the list
REAL(dates)[i++] = item->dateTime.toUTC().toSecsSinceEpoch();
}
// POSIXct class
PROTECT(clas=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(dates,clas);
// we use "UTC" for all timezone
Rf_setAttrib(dates, Rf_install("tzone"), Rf_mkString("UTC"));
UNPROTECT(2);
}
return dates;
}
SEXP
RTool::dfForRideItem(const RideItem *ri)
{
RideItem *item = const_cast<RideItem*>(ri);
const RideMetricFactory &factory = RideMetricFactory::instance();
int rides = rtool->context->athlete->rideCache->count();
int metrics = factory.metricCount();
// count the number of meta fields to add
int meta = 0;
if (rtool->context) {
// count active fields
foreach(FieldDefinition def, GlobalContext::context()->rideMetadata->getFields()) {
if (def.name != "" && def.tab != "" &&
!SpecialFields::getInstance().isMetric(def.name))
meta++;
}
}
// just this ride !
rides = 1;
// get a listAllocated
SEXP ans;
SEXP names; // column names
SEXP rownames; // row names (numeric)
// +3 is for date and datetime and color
PROTECT(ans=Rf_allocVector(VECSXP, metrics+meta+3));
PROTECT(names = Rf_allocVector(STRSXP, metrics+meta+3));
// we have to give a name to each row
PROTECT(rownames = Rf_allocVector(STRSXP, rides));
for(int i=0; i<rides; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// next name, nextS is next metric
int next=0;
// DATE
SEXP date;
PROTECT(date=Rf_allocVector(INTSXP, rides));
QDate d1970(1970,01,01);
INTEGER(date)[0] = d1970.daysTo(item->dateTime.date());
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(date,dclas);
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, date);
SET_STRING_ELT(names, next++, Rf_mkChar("date"));
// TIME
SEXP time;
PROTECT(time=Rf_allocVector(REALSXP, rides));
REAL(time)[0] = item->dateTime.toUTC().toSecsSinceEpoch();
// POSIXct class
SEXP clas;
PROTECT(clas=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(time,clas);
// we use "UTC" for all timezone
Rf_setAttrib(time, Rf_install("tzone"), Rf_mkString("UTC"));
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, time);
SET_STRING_ELT(names, next++, Rf_mkChar("time"));
// time + clas, but not ans!
UNPROTECT(4);
//
// METRICS
//
for(int i=0; i<factory.metricCount();i++) {
// set a vector
SEXP m;
PROTECT(m=Rf_allocVector(REALSXP, rides));
QString symbol = factory.metricName(i);
const RideMetric *metric = factory.rideMetric(symbol);
QString name = SpecialFields::getInstance().internalName(factory.rideMetric(symbol)->name());
name = name.replace(" ","_");
name = name.replace("'","_");
bool useMetricUnits = GlobalContext::context()->useMetricUnits;
REAL(m)[0] = item->metrics()[i] * (useMetricUnits ? 1.0f : metric->conversion()) + (useMetricUnits ? 0.0f : metric->conversionSum());
// add to the list
SET_VECTOR_ELT(ans, next, m);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(name.toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
}
//
// META
//
foreach(FieldDefinition field, GlobalContext::context()->rideMetadata->getFields()) {
// don't add incomplete meta definitions or metric override fields
if (field.name == "" || field.tab == "" ||
SpecialFields::getInstance().isMetric(field.name)) continue;
// Create a string vector
SEXP m;
PROTECT(m=Rf_allocVector(STRSXP, rides));
SET_STRING_ELT(m, 0, Rf_mkChar(item->getText(field.name, "").toLatin1().constData()));
// add to the list
SET_VECTOR_ELT(ans, next, m);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(field.name.replace(" ","_").toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
}
// add Color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, rides));
// apply item color, remembering that 1,1,1 means use default (reverse in this case)
if (item->color == QColor(1,1,1,1)) {
// use the inverted color, not plot marker as that hideous
QColor col =GCColor::invertColor(GColor(CPLOTBACKGROUND));
// white is jarring on a dark background!
if (col==QColor(Qt::white)) col=QColor(127,127,127);
SET_STRING_ELT(color, 0, Rf_mkChar(col.name().toLatin1().constData()));
} else
SET_STRING_ELT(color, 0, Rf_mkChar(item->color.name().toLatin1().constData()));
// add to the list and name it
SET_VECTOR_ELT(ans, next, color);
SET_STRING_ELT(names, next, Rf_mkChar("color"));
next++;
UNPROTECT(1);
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
// ans + names
UNPROTECT(3);
// return it
return ans;
}
SEXP
RTool::dfForDateRange(bool all, DateRange range, SEXP filter)
{
const RideMetricFactory &factory = RideMetricFactory::instance();
int rides = rtool->context->athlete->rideCache->count();
int metrics = factory.metricCount();
// count the number of meta fields to add
int meta = 0;
if (rtool->context) {
// count active fields
foreach(FieldDefinition def, GlobalContext::context()->rideMetadata->getFields()) {
if (def.name != "" && def.tab != "" &&
!SpecialFields::getInstance().isMetric(def.name))
meta++;
}
}
// how many rides to return if we're limiting to the
// currently selected date range ?
// apply any global filters
Specification specification;
FilterSet fs;
fs.addFilter(rtool->context->isfiltered, rtool->context->filters);
fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters);
fs.addFilter(rtool->perspective->isFiltered(), rtool->perspective->filterlist(range));
specification.setFilterSet(fs);
// did call contain any filters?
PROTECT(filter=Rf_coerceVector(filter, STRSXP));
for(int i=0; i<Rf_length(filter); i++) {
// if not empty write a filter
QString f(CHAR(STRING_ELT(filter,i)));
if (f != "") {
DataFilter dataFilter(rtool->canvas, rtool->context);
QStringList files;
dataFilter.parseFilter(rtool->context, f, &files);
fs.addFilter(true, files);
}
}
specification.setFilterSet(fs);
UNPROTECT(1);
// we need to count rides that are in range...
rides = 0;
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (all || range.pass(ride->dateTime.date())) rides++;
}
// get a listAllocated
SEXP ans;
SEXP names; // column names
SEXP rownames; // row names (numeric)
// +3 is for date and datetime and color
PROTECT(ans=Rf_allocVector(VECSXP, metrics+meta+3));
PROTECT(names = Rf_allocVector(STRSXP, metrics+meta+3));
// we have to give a name to each row
PROTECT(rownames = Rf_allocVector(STRSXP, rides));
for(int i=0; i<rides; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// next name
int next=0;
// DATE
SEXP date;
PROTECT(date=Rf_allocVector(INTSXP, rides));
int k=0;
QDate d1970(1970,01,01);
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (all || range.pass(ride->dateTime.date()))
INTEGER(date)[k++] = d1970.daysTo(ride->dateTime.date());
}
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(date,dclas);
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, date);
SET_STRING_ELT(names, next++, Rf_mkChar("date"));
// TIME
SEXP time;
PROTECT(time=Rf_allocVector(REALSXP, rides));
// fill with values for date and class if its one we need to return
k=0;
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (all || range.pass(ride->dateTime.date()))
REAL(time)[k++] = ride->dateTime.toUTC().toSecsSinceEpoch();
}
// POSIXct class
SEXP clas;
PROTECT(clas=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(time,clas);
// we use "UTC" for all timezone
Rf_setAttrib(time, Rf_install("tzone"), Rf_mkString("UTC"));
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, time);
SET_STRING_ELT(names, next++, Rf_mkChar("time"));
// time + clas, but not ans!
UNPROTECT(4);
//
// METRICS
//
for(int i=0; i<factory.metricCount();i++) {
// set a vector
SEXP m;
PROTECT(m=Rf_allocVector(REALSXP, rides));
QString symbol = factory.metricName(i);
const RideMetric *metric = factory.rideMetric(symbol);
QString name = SpecialFields::getInstance().internalName(factory.rideMetric(symbol)->name());
name = name.replace(" ","_");
name = name.replace("'","_");
bool useMetricUnits = GlobalContext::context()->useMetricUnits;
int index=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(item)) continue;
if (all || range.pass(item->dateTime.date())) {
REAL(m)[index++] = item->metrics()[i] * (useMetricUnits ? 1.0f : metric->conversion())
+ (useMetricUnits ? 0.0f : metric->conversionSum());
}
}
// add to the list
SET_VECTOR_ELT(ans, next, m);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(name.toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
}
//
// META
//
foreach(FieldDefinition field, GlobalContext::context()->rideMetadata->getFields()) {
// don't add incomplete meta definitions or metric override fields
if (field.name == "" || field.tab == "" ||
SpecialFields::getInstance().isMetric(field.name)) continue;
// Create a string vector
SEXP m;
PROTECT(m=Rf_allocVector(STRSXP, rides));
int index=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(item)) continue;
if (all || range.pass(item->dateTime.date())) {
SET_STRING_ELT(m, index++, Rf_mkChar(item->getText(field.name, "").toLatin1().constData()));
}
}
// add to the list
SET_VECTOR_ELT(ans, next, m);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(field.name.replace(" ","_").toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
}
// add Color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, rides));
int index=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(item)) continue;
if (all || range.pass(item->dateTime.date())) {
// apply item color, remembering that 1,1,1 means use default (reverse in this case)
if (item->color == QColor(1,1,1,1)) {
// use the inverted color, not plot marker as that hideous
QColor col =GCColor::invertColor(GColor(CPLOTBACKGROUND));
// white is jarring on a dark background!
if (col==QColor(Qt::white)) col=QColor(127,127,127);
SET_STRING_ELT(color, index++, Rf_mkChar(col.name().toLatin1().constData()));
} else
SET_STRING_ELT(color, index++, Rf_mkChar(item->color.name().toLatin1().constData()));
}
}
// add to the list and name it
SET_VECTOR_ELT(ans, next, color);
SET_STRING_ELT(names, next, Rf_mkChar("color"));
next++;
UNPROTECT(1);
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
// ans + names
UNPROTECT(3);
// return it
return ans;
}
SEXP
RTool::dfForDateRangeIntervals(DateRange range, QStringList types)
{
const RideMetricFactory &factory = RideMetricFactory::instance();
int intervals = 0;
int metrics = factory.metricCount();
// how many rides to return if we're limiting to the
// currently selected date range ?
// apply any global filters
Specification specification;
FilterSet fs;
fs.addFilter(rtool->context->isfiltered, rtool->context->filters);
fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters);
fs.addFilter(rtool->perspective->isFiltered(), rtool->perspective->filterlist(range));
specification.setFilterSet(fs);
// we need to count intervals that are in range...
intervals = 0;
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (!range.pass(ride->dateTime.date())) continue;
if (types.isEmpty()) intervals += ride->intervals().count();
else {
foreach(IntervalItem *item, ride->intervals())
if (types.contains(RideFileInterval::typeDescription(item->type)))
intervals++;
}
}
// get a listAllocated
SEXP ans;
SEXP names; // column names
SEXP rownames; // row names (numeric)
// +5 is for date and datetime, name, type and color
PROTECT(ans=Rf_allocVector(VECSXP, metrics+5));
PROTECT(names = Rf_allocVector(STRSXP, metrics+5));
// we have to give a name to each row
PROTECT(rownames = Rf_allocVector(STRSXP, intervals));
for(int i=0; i<intervals; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// next name
int next=0;
// DATE
SEXP date;
PROTECT(date=Rf_allocVector(INTSXP, intervals));
int k=0;
QDate d1970(1970,01,01);
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (range.pass(ride->dateTime.date())) {
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
INTEGER(date)[k++] = d1970.daysTo(ride->dateTime.date());
}
}
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(date,dclas);
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, date);
SET_STRING_ELT(names, next++, Rf_mkChar("date"));
// TIME
SEXP time;
PROTECT(time=Rf_allocVector(REALSXP, intervals));
// fill with values for date and class if its one we need to return
k=0;
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (range.pass(ride->dateTime.date())) {
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
REAL(time)[k++] = ride->dateTime.toUTC().toSecsSinceEpoch() + item->start; // time offsets by time of interval
}
}
// POSIXct class
SEXP clas;
PROTECT(clas=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(time,clas);
// we use "UTC" for all timezone
Rf_setAttrib(time, Rf_install("tzone"), Rf_mkString("UTC"));
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, time);
SET_STRING_ELT(names, next++, Rf_mkChar("time"));
// NAME
SEXP intervalnames;
PROTECT(intervalnames = Rf_allocVector(STRSXP, intervals));
k=0;
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (range.pass(ride->dateTime.date())) {
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
SET_STRING_ELT(intervalnames, k++, Rf_mkChar(item->name.toLatin1().constData()));
}
}
// add to the list and give a columnname
SET_VECTOR_ELT(ans, next, intervalnames);
SET_STRING_ELT(names, next, Rf_mkChar("name"));
next++;
// TYPE
SEXP intervaltypes;
PROTECT(intervaltypes = Rf_allocVector(STRSXP, intervals));
k=0;
foreach(RideItem *ride, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(ride)) continue;
if (range.pass(ride->dateTime.date())) {
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
SET_STRING_ELT(intervaltypes, k++, Rf_mkChar(RideFileInterval::typeDescription(item->type).toLatin1().constData()));
}
}
SET_VECTOR_ELT(ans, next, intervaltypes);
SET_STRING_ELT(names, next, Rf_mkChar("type"));
next++;
// time + clas + name + type, but not ans!
UNPROTECT(6);
//
// METRICS
//
for(int i=0; i<factory.metricCount();i++) {
// set a vector
SEXP m;
PROTECT(m=Rf_allocVector(REALSXP, intervals));
QString symbol = factory.metricName(i);
const RideMetric *metric = factory.rideMetric(symbol);
QString name = SpecialFields::getInstance().internalName(factory.rideMetric(symbol)->name());
name = name.replace(" ","_");
name = name.replace("'","_");
bool useMetricUnits = GlobalContext::context()->useMetricUnits;
int index=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(item)) continue;
if (range.pass(item->dateTime.date())) {
foreach(IntervalItem *interval, item->intervals()) {
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(interval->type)))
REAL(m)[index++] = interval->metrics()[i] * (useMetricUnits ? 1.0f : metric->conversion())
+ (useMetricUnits ? 0.0f : metric->conversionSum());
}
}
}
// add to the list
SET_VECTOR_ELT(ans, next, m);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(name.toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
}
// add Color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, intervals));
int index=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
if (!specification.pass(item)) continue;
if (!range.pass(item->dateTime.date())) continue;
foreach(IntervalItem *interval, item->intervals()) {
if (!types.isEmpty() && !types.contains(RideFileInterval::typeDescription(interval->type))) continue;
// apply item color, remembering that 1,1,1 means use default (reverse in this case)
if (interval->color == QColor(1,1,1,1)) {
// use the inverted color, not plot marker as that hideous
QColor col =GCColor::invertColor(GColor(CPLOTBACKGROUND));
// white is jarring on a dark background!
if (col==QColor(Qt::white)) col=QColor(127,127,127);
SET_STRING_ELT(color, index++, Rf_mkChar(col.name().toLatin1().constData()));
} else
SET_STRING_ELT(color, index++, Rf_mkChar(interval->color.name().toLatin1().constData()));
}
}
// add to the list and name it
SET_VECTOR_ELT(ans, next, color);
SET_STRING_ELT(names, next, Rf_mkChar("color"));
next++;
UNPROTECT(1);
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
// ans + names
UNPROTECT(3);
// return it
return ans;
}
// returns a data frame of season info
SEXP
RTool::season(SEXP pAll, SEXP pCompare)
{
// p1 - all=TRUE|FALSE - return all metrics or just within
// the currently selected date range
pAll = Rf_coerceVector(pAll, LGLSXP);
bool all = LOGICAL(pAll)[0];
// p2 - all=TRUE|FALSE - return list of compares (or current if not active)
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// data frame for season: color, name, start, end
// XXX TODO type needs adding, but we need to unpick the
// phase/season object model first, will do later
SEXP df;
PROTECT(df=Rf_allocVector(VECSXP, 4));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 4));
SET_STRING_ELT(names, 0, Rf_mkChar("name"));
SET_STRING_ELT(names, 1, Rf_mkChar("start"));
SET_STRING_ELT(names, 2, Rf_mkChar("end"));
SET_STRING_ELT(names, 3, Rf_mkChar("color"));
// worklist of date ranges to return
// XXX TODO use a Season worklist one the phase/season
// object model is fixed
QList<DateRange> worklist;
if (compare) {
// return a list, even if just one
if (rtool->context->isCompareDateRanges) {
foreach(CompareDateRange p, rtool->context->compareDateRanges)
worklist << DateRange(p.start, p.end, p.name, p.color);
} else {
// if compare not active just return current selection
worklist << rtool->context->currentDateRange();
}
} else if (all) {
// list all seasons
foreach(Season season, rtool->context->athlete->seasons->seasons) {
worklist << DateRange(season.getStart(), season.getEnd(), season.getName(), QColor(127,127,127));
}
} else {
// just the currently selected season please
worklist << rtool->context->currentDateRange();
}
SEXP rownames, start, end, name, color;
PROTECT(start=Rf_allocVector(INTSXP, worklist.count()));
PROTECT(end=Rf_allocVector(INTSXP, worklist.count()));
PROTECT(name=Rf_allocVector(STRSXP, worklist.count()));
PROTECT(color=Rf_allocVector(STRSXP, worklist.count()));
PROTECT(rownames = Rf_allocVector(STRSXP, worklist.count()));
int index=0;
QDate d1970(1970,1,1);
foreach(DateRange p, worklist){
INTEGER(start) [index] = d1970.daysTo(p.from);
INTEGER(end) [index] = d1970.daysTo(p.to);
SET_STRING_ELT(name, index, Rf_mkChar(p.name.toLatin1().constData()));
SET_STRING_ELT(color, index, Rf_mkChar(p.color.name().toLatin1().constData()));
QString rownumber=QString("%1").arg(index+1);
SET_STRING_ELT(rownames, index, Rf_mkChar(rownumber.toLatin1().constData()));
index++;
}
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(start,dclas);
Rf_classgets(end,dclas);
SET_VECTOR_ELT(df, 0, name);
SET_VECTOR_ELT(df, 1, start);
SET_VECTOR_ELT(df, 2, end);
SET_VECTOR_ELT(df, 3, color);
// list into a data.frame
Rf_namesgets(df, names);
Rf_setAttrib(df, R_RowNamesSymbol, rownames);
Rf_setAttrib(df, R_ClassSymbol, Rf_mkString("data.frame"));
UNPROTECT(8); // df + names
// fail
return df;
}
SEXP
RTool::seasonIntervals(SEXP pTypes, SEXP pCompare)
{
// p1 - type of intervals to get (vector of strings)
// p2 - compare mode (true or false)
pTypes = Rf_coerceVector(pTypes, STRSXP);
QStringList types;
for(int i=0; i<Rf_length(pTypes); i++)
types << QString(CHAR(STRING_ELT(pTypes,i)));
//pType = Rf_coerceVector(pAll, LGLSXP);
//bool all = LOGICAL(pAll)[0];
// p2 - all=TRUE|FALSE - return list of compares (or current if not active)
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// want a list of compares not a dataframe
if (compare && rtool->context) {
// only return compares if its actually active
if (rtool->context->isCompareDateRanges) {
// how many to return?
int count=0;
foreach(CompareDateRange p, rtool->context->compareDateRanges) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int index=0;
// a named list with data.frame 'intervals' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("intervals"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareDateRange p, rtool->context->compareDateRanges) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForDateRangeIntervals(DateRange(p.start, p.end), types);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, index++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else { // compare isn't active...
// otherwise return the current metrics in a compare list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("intervals"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of metrics and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the metrics
DateRange range = rtool->context->currentDateRange();
SEXP df = rtool->dfForDateRangeIntervals(range, types);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (rtool->context && rtool->context->athlete && rtool->context->athlete->rideCache) {
// just a datafram of metrics
DateRange range = rtool->context->currentDateRange();
return rtool->dfForDateRangeIntervals(range, types);
}
// fail
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::intervalType(SEXP type)
{
type = Rf_coerceVector(type, INTSXP);
QString typeDesc = RideFileInterval::typeDescription(static_cast<RideFileInterval::IntervalType>(INTEGER(type)[0]));
SEXP ans;
PROTECT(ans=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(ans, 0, Rf_mkChar(typeDesc.toLatin1().constData()));
UNPROTECT(1);
return ans;
}
SEXP
RTool::activityIntervals(SEXP pTypes, SEXP datetime)
{
// p1 - type of intervals to get (vector of strings)
// p2 - activity (datetime)
pTypes = Rf_coerceVector(pTypes, STRSXP);
QStringList types;
for(int i=0; i<Rf_length(pTypes); i++)
types << QString(CHAR(STRING_ELT(pTypes,i)));
// get an activity to process
RideItem* ride;
QList<RideItem*>activities = rtool->activitiesFor(datetime);
if (activities.count()) ride = activities[0];
else ride = const_cast<RideItem*>(rtool->context->currentRideItem());
// if no current ride or more than one activity requested, nothing to return
if (ride == NULL || activities.count() > 1) return Rf_allocVector(INTSXP, 0);
const RideMetricFactory &factory = RideMetricFactory::instance();
int intervals = 0;
int metrics = factory.metricCount();
// we need to count intervals that are in range...
if (types.isEmpty()) intervals = ride->intervals().count();
else {
foreach(IntervalItem *item, ride->intervals())
if (types.contains(RideFileInterval::typeDescription(item->type)))
intervals++;
}
// get a listAllocated
SEXP ans;
SEXP names; // column names
SEXP rownames; // row names (numeric)
// +6 is for start and stop, name, type, color and selected
PROTECT(ans=Rf_allocVector(VECSXP, metrics+6));
PROTECT(names = Rf_allocVector(STRSXP, metrics+6));
// we have to give a name to each row
PROTECT(rownames = Rf_allocVector(STRSXP, intervals));
for(int i=0; i<intervals; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// next name
int next=0;
// START
SEXP start;
PROTECT(start=Rf_allocVector(REALSXP, intervals));
int k=0;
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
REAL(start)[k++] = item->start;
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, start);
SET_STRING_ELT(names, next++, Rf_mkChar("start"));
// STOP
SEXP stop;
PROTECT(stop=Rf_allocVector(REALSXP, intervals));
k=0;
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
REAL(stop)[k++] = item->stop;
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, stop);
SET_STRING_ELT(names, next++, Rf_mkChar("stop"));
// NAME
SEXP intervalnames;
PROTECT(intervalnames = Rf_allocVector(STRSXP, intervals));
k=0;
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
SET_STRING_ELT(intervalnames, k++, Rf_mkChar(item->name.toLatin1().constData()));
// add to the list and give a columnname
SET_VECTOR_ELT(ans, next, intervalnames);
SET_STRING_ELT(names, next, Rf_mkChar("name"));
next++;
// TYPE
SEXP intervaltypes;
PROTECT(intervaltypes = Rf_allocVector(STRSXP, intervals));
k=0;
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
SET_STRING_ELT(intervaltypes, k++, Rf_mkChar(RideFileInterval::typeDescription(item->type).toLatin1().constData()));
SET_VECTOR_ELT(ans, next, intervaltypes);
SET_STRING_ELT(names, next, Rf_mkChar("type"));
next++;
// SELECTED
SEXP selected;
PROTECT(selected=Rf_allocVector(LGLSXP, intervals));
k=0;
foreach(IntervalItem *item, ride->intervals())
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(item->type)))
LOGICAL(selected)[k++] = item->selected;
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, selected);
SET_STRING_ELT(names, next++, Rf_mkChar("selected"));
// start + stop + name + type + selected, but not ans!
UNPROTECT(5);
//
// METRICS
//
for(int i=0; i<factory.metricCount();i++) {
// set a vector
SEXP m;
PROTECT(m=Rf_allocVector(REALSXP, intervals));
QString symbol = factory.metricName(i);
const RideMetric *metric = factory.rideMetric(symbol);
QString name = SpecialFields::getInstance().internalName(factory.rideMetric(symbol)->name());
name = name.replace(" ","_");
name = name.replace("'","_");
bool useMetricUnits = GlobalContext::context()->useMetricUnits;
int index=0;
foreach(IntervalItem *interval, ride->intervals()) {
if (types.isEmpty() || types.contains(RideFileInterval::typeDescription(interval->type)))
REAL(m)[index++] = interval->metrics()[i] * (useMetricUnits ? 1.0f : metric->conversion())
+ (useMetricUnits ? 0.0f : metric->conversionSum());
}
// add to the list
SET_VECTOR_ELT(ans, next, m);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(name.toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
}
// add Color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, intervals));
int index=0;
foreach(IntervalItem *interval, ride->intervals()) {
if (!types.isEmpty() && !types.contains(RideFileInterval::typeDescription(interval->type))) continue;
// apply item color, remembering that 1,1,1 means use default (reverse in this case)
if (interval->color == QColor(1,1,1,1)) {
// use the inverted color, not plot marker as that hideous
QColor col =GCColor::invertColor(GColor(CPLOTBACKGROUND));
// white is jarring on a dark background!
if (col==QColor(Qt::white)) col=QColor(127,127,127);
SET_STRING_ELT(color, index++, Rf_mkChar(col.name().toLatin1().constData()));
} else
SET_STRING_ELT(color, index++, Rf_mkChar(interval->color.name().toLatin1().constData()));
}
// add to the list and name it
SET_VECTOR_ELT(ans, next, color);
SET_STRING_ELT(names, next, Rf_mkChar("color"));
next++;
UNPROTECT(1);
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
// ans + names
UNPROTECT(3);
// return it
return ans;
}
SEXP
RTool::metrics(SEXP pAll, SEXP pFilter, SEXP pCompare)
{
// p1 - all=TRUE|FALSE - return all metrics or just within
// the currently selected date range
pAll = Rf_coerceVector(pAll, LGLSXP);
bool all = LOGICAL(pAll)[0];
// p2 - all=TRUE|FALSE - return list of compares (or current if not active)
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// want a list of compares not a dataframe
if (compare && rtool->context) {
// only return compares if its actually active
if (rtool->context->isCompareDateRanges) {
// how many to return?
int count=0;
foreach(CompareDateRange p, rtool->context->compareDateRanges) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int index=0;
// a named list with data.frame 'metrics' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("metrics"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareDateRange p, rtool->context->compareDateRanges) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForDateRange(all, DateRange(p.start, p.end), pFilter);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, index++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else { // compare isn't active...
// otherwise return the current metrics in a compare list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("metrics"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of metrics and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the metrics
DateRange range = rtool->context->currentDateRange();
SEXP df = rtool->dfForDateRange(all, range, pFilter);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (rtool->context && rtool->context->athlete && rtool->context->athlete->rideCache) {
// just a datafram of metrics
DateRange range = rtool->context->currentDateRange();
return rtool->dfForDateRange(all, range, pFilter);
}
// fail
return Rf_allocVector(INTSXP, 0);
}
QList<SEXP>
RTool::dfForActivity(RideFile *f, int split, QString join)
{
// return a data frame for the ride passed
QList<SEXP> returning;
// how many series?
int seriescount=0;
for(int i=0; i<static_cast<int>(RideFile::none); i++) {
RideFile::SeriesType series = static_cast<RideFile::SeriesType>(i);
if (i > 15 && !f->isDataPresent(series)) continue;
seriescount++;
}
// add xdata to the series count
QMapIterator<QString, XDataSeries *> it(f->xdata());
it.toFront();
while(it.hasNext()) {
it.next();
seriescount += it.value()->valuename.count();
}
// if we have any series we will continue and add a 'time' series
if (seriescount) seriescount++;
else return returning;
// start at first sample in ride
int index=0;
int pcount=0;
while(index < f->dataPoints().count()) {
// we return a list of series vectors
SEXP ans = PROTECT(Rf_allocVector(VECSXP, seriescount));
pcount++;
// we collect the names as we go
SEXP names = PROTECT(Rf_allocVector(STRSXP, seriescount)); // names attribute (column names)
int next=0;
pcount++;
//
// We might need to split...
//
// do we stop at the end, or mid-ride ?
int stop = f->dataPoints().count();
if (split) {
for(int i=index+1; i<f->dataPoints().count(); i++) {
if (i && (f->dataPoints()[i]->secs - f->dataPoints()[i-1]->secs) > double(split)) {
stop = i;
goto outer;
}
}
}
outer:
int points = stop - index;
// TIME
// add in actual time in POSIXct format
SEXP time = PROTECT(Rf_allocVector(REALSXP, points));
pcount++;
// fill with values for date and class
for(int k=0; k<points; k++) REAL(time)[k] = f->startTime().addSecs(f->dataPoints()[index+k]->secs).toUTC().toSecsSinceEpoch();
// POSIXct class
SEXP clas = PROTECT(Rf_allocVector(STRSXP, 2));
pcount++;
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(time,clas);
// we use "UTC" for all timezone
Rf_setAttrib(time, Rf_install("tzone"), Rf_mkString("UTC"));
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, time);
SET_STRING_ELT(names, next++, Rf_mkChar("time"));
// PRESENT SERIES
for(int s=0; s < static_cast<int>(RideFile::none); s++) {
// what series we working with?
RideFile::SeriesType series = static_cast<RideFile::SeriesType>(s);
// lets not add lots of NA for the more obscure data series
if (s > 15 && !f->isDataPresent(series)) continue;
// set a vector
SEXP vector = PROTECT(Rf_allocVector(REALSXP, points));
pcount++;
for(int j=index; j<stop; j++) {
if (f->isDataPresent(series)) {
if (f->dataPoints()[j]->value(series) == 0 && (series == RideFile::lat || series == RideFile::lon))
REAL(vector)[j-index] = NA_REAL;
else
REAL(vector)[j-index] = f->dataPoints()[j]->value(series);
} else {
REAL(vector)[j-index] = NA_REAL;
}
}
// add to the list
SET_VECTOR_ELT(ans, next, vector);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(f->seriesName(series, true).toLatin1().constData()));
next++;
}
// XDATA SERIES
RideFile::XDataJoin xjoin;
xjoin = RideFile::REPEAT;
QStringList xdataValidSymbols;
xdataValidSymbols << "sparse" << "repeat" << "interpolate" << "resample";
int xx = xdataValidSymbols.indexOf(join, Qt::CaseInsensitive);
switch(xx) {
case 0: xjoin = RideFile::SPARSE; break;
default:
case 1: xjoin = RideFile::REPEAT; break;
case 2: xjoin = RideFile::INTERPOLATE; break;
case 3: xjoin = RideFile::RESAMPLE; break;
}
it.toFront(); // reused from above
while(it.hasNext()) {
it.next();
if (it.value()->valuename.count() == 0) continue;
// add a series for every one
foreach(QString series, it.value()->valuename) {
// set a vector
SEXP vector = PROTECT(Rf_allocVector(REALSXP, points));
pcount++;
int idx=0;
for(int j=index; j<stop; j++) {
RideFilePoint *p = f->dataPoints()[j];
double val = f->xdataValue(p, idx, it.value()->name, series, xjoin);
REAL(vector)[j-index] = (val == RideFile::NA) ? NA_REAL : val;
}
// add to the list
SET_VECTOR_ELT(ans, next, vector);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(QString("%1_%2").arg(it.value()->name).arg(series.replace(" ","_")).toLatin1().constData()));
next++;
}
}
// add rownames
SEXP rownames = PROTECT(Rf_allocVector(STRSXP, points));
pcount++;
for(int i=0; i<points; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_namesgets(ans, names);
// jump to where we got
index = stop;
returning << ans;
}
UNPROTECT(pcount);
// return a valid result
return returning;
}
QList<RideItem *>
RTool::activitiesFor(SEXP datetime)
{
QList<RideItem*> returning;
PROTECT(datetime=Rf_coerceVector(datetime, INTSXP));
for(int i=0; i<Rf_length(datetime); i++) {
long dt = INTEGER(datetime)[i];
if (dt==0) continue;
// we need to find this one !
QDateTime asdt = QDateTime::fromSecsSinceEpoch(dt);
foreach(RideItem*item, rtool->context->athlete->rideCache->rides()) {
if (item->dateTime.toUTC() == asdt.toUTC()) {
returning << const_cast<RideItem*>(item);
break;
}
}
}
UNPROTECT(1);
// return a list of activities to process
return returning;
}
SEXP
RTool::activity(SEXP datetime, SEXP pCompare, SEXP pSplit, SEXP pJoin)
{
// p1 - compare=TRUE|FALSE - return list of compare rides if active, or just current
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
pJoin = Rf_coerceVector(pJoin, STRSXP);
QString join(CHAR(STRING_ELT(pJoin,0)));
// get a list of activitie to return - user specified ALWAYS gets a list
// even if they only provided a single date to process
bool userlist = false;
QList<RideItem*>activities = rtool->activitiesFor(datetime);
if (activities.count()) userlist=true; // use compare mode code to create a list of rides
// p3 split in seconds, 0=means no split
pSplit = Rf_coerceVector(pSplit, INTSXP);
int split = INTEGER(pSplit)[0];
// user requested specific activities?
if (userlist) {
// we collect a list to return, appending as we go, rather
// than pre-allocating, since we decide to split and may
// get multiple responses
QList<SEXP> f;
// create a data.frame for each and add to list
foreach(RideItem *item, activities) {
// we DO NOT use R_CheckUserInterrupt since it longjmps
// and leaves quite a mess behind. We check ourselves
// if a cancel was requested we honour it
QApplication::processEvents();
if (rtool->cancelled) break;
// we open, if it wasn't open we also close
// to make sure we don't exhause memory
bool close = (item->isOpen() == false);
foreach(SEXP df, rtool->dfForActivity(item->ride(), split, join)) f<<df;
if (close) item->close();
}
// now create an R list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, f.count()));
for(int index=0; index < f.count(); index++) SET_VECTOR_ELT(list, index, f[index]);
// we have to give a name to each row
SEXP rownames;
PROTECT(rownames = Rf_allocVector(STRSXP, f.count()));
for(int i=0; i<f.count(); i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
//XXX Do not create a dataframe of dataframes, R doesn't like these
//XXX Rf_setAttrib(list, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(list, R_RowNamesSymbol, rownames);
Rf_namesgets(list, rownames);
UNPROTECT(3); // list and names and rownames
return list;
} else if ((split || compare) && rtool->context) { // split or compare will generate a list
if (compare && rtool->context->isCompareIntervals) {
// how many to return?
int count=0;
foreach(CompareInterval p, rtool->context->compareIntervals) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
QList<SEXP> f;
// a named list with data.frame 'activity' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("activity"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareInterval p, rtool->context->compareIntervals) {
if (p.isChecked()) {
foreach(SEXP df, rtool->dfForActivity(p.rideItem->ride(), split, join)) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
f << namedlist;
UNPROTECT(2);
}
}
}
// now create an R list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, f.count()));
for(int index=0; index < f.count(); index++) SET_VECTOR_ELT(list, index, f[index]);
UNPROTECT(2); // list and names
return list;
} else if (rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// just return a list of one ride
// cool we can return a list of intervals to compare
QList<SEXP> files;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("activity"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// add the ride
RideFile *f = const_cast<RideItem*>(rtool->context->currentRideItem())->ride();
f->recalculateDerivedSeries();
foreach(SEXP df, rtool->dfForActivity(f, split, join)) {
// named list of activity and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
files << namedlist;
UNPROTECT(2);
}
// now create an R list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, files.count()));
for(int index=0; index < files.count(); index++) SET_VECTOR_ELT(list, index, files[index]);
UNPROTECT(2);
return list;
}
} else if (!split && !compare) { // not compare, so just return a dataframe
// access via global as this is a static function
if(rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// get the ride
RideFile *f = const_cast<RideItem*>(rtool->context->currentRideItem())->ride();
f->recalculateDerivedSeries();
// get as a data frame
QList<SEXP> returning = rtool->dfForActivity(f, 0, join);
if (returning.count()) return returning[0];
}
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::dfForActivityMeanmax(const RideItem *i)
{
return dfForRideFileCache(const_cast<RideItem*>(i)->fileCache());
}
SEXP
RTool::dfForDateRangeMeanmax(bool all, DateRange range, SEXP filter)
{
// construct the date range and then get a ridefilecache
if (all) range = DateRange(QDate(1900,01,01), QDate(2100,01,01));
// did call contain any filters?
QStringList filelist;
bool filt=false;
PROTECT(filter=Rf_coerceVector(filter, STRSXP));
for(int i=0; i<Rf_length(filter); i++) {
// if not empty write a filter
QString f(CHAR(STRING_ELT(filter,i)));
if (f != "") {
DataFilter dataFilter(rtool->canvas, rtool->context);
QStringList files;
dataFilter.parseFilter(rtool->context, f, &files);
filelist << files;
filt=true;
}
}
UNPROTECT(1);
// apply perspective filter if trends view and filtered
if (rtool->perspective && rtool->perspective->type() == VIEW_TRENDS && rtool->perspective->isFiltered()) {
filt = true;
filelist << rtool->perspective->filterlist(DateRange(range));
}
// RideFileCache for a date range with our filters (if any)
RideFileCache cache(rtool->context, range.from, range.to, filt, filelist, true, NULL);
return dfForRideFileCache(&cache);
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::dfForRideFileCache(RideFileCache *cache)
{
// how many series and how big are they?
unsigned int seriescount=0, size=0;
// get the meanmax array
if (cache != NULL) {
// how many points in the ridefilecache and how many series to return
foreach(RideFile::SeriesType series, cache->meanMaxList()) {
QVector <double> values = cache->meanMaxArray(series);
if (values.count()) {
if (static_cast<unsigned int>(values.count()) > size) size = values.count();
seriescount++;
}
if (series==RideFile::watts) {
seriescount++; // add powerdate
}
}
} else {
// fail
return Rf_allocVector(INTSXP, 0);
}
// we return a list of series vectors
SEXP ans;
PROTECT(ans = Rf_allocVector(VECSXP, seriescount));
// we collect the names as we go
SEXP names;
PROTECT(names = Rf_allocVector(STRSXP, seriescount)); // names attribute (column names)
int next=0;
//
// Now we need to add vectors to the ans list...
//
foreach(RideFile::SeriesType series, cache->meanMaxList()) {
QVector <double> values = cache->meanMaxArray(series);
// don't add empty ones
if (values.count()==0) continue;
// set a vector
SEXP vector;
PROTECT(vector=Rf_allocVector(REALSXP, values.count()));
// will have different sizes e.g. when a daterange
// since longest ride with e.g. power may be different
// to longest ride with heartrate
for(int j=0; j<values.count(); j++) REAL(vector)[j] = values[j];
// add to the list
SET_VECTOR_ELT(ans, next, vector);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar(RideFile::seriesName(series, true).toLatin1().constData()));
next++;
// vector
UNPROTECT(1);
// if is power add the dates
if(series == RideFile::watts) {
QDate d1970(1970,01,01);
// dates
QVector<QDate> dates = cache->meanMaxDates(series);
SEXP vector;
PROTECT(vector=Rf_allocVector(INTSXP, values.count()));
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(vector,dclas);
// will have different sizes e.g. when a daterange
// since longest ride with e.g. power may be different
// to longest ride with heartrate
for(int j=0; j<values.count(); j++) INTEGER(vector)[j] = d1970.daysTo(dates[j]);
// add to the list
SET_VECTOR_ELT(ans, next, vector);
// give it a name
SET_STRING_ELT(names, next, Rf_mkChar("power_date"));
next++;
// vector
UNPROTECT(2);
}
}
// add rownames
SEXP rownames;
PROTECT(rownames = Rf_allocVector(STRSXP, size));
for(unsigned int i=0; i<size; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
//Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_namesgets(ans, names);
// ans + names + rownames
UNPROTECT(3);
// return a valid result
return ans;
}
SEXP
RTool::seasonPeaks(SEXP pAll, SEXP pFilter, SEXP pCompare, SEXP pSeries, SEXP pDuration)
{
// check parameters !
pAll = Rf_coerceVector(pAll, LGLSXP);
bool all = LOGICAL(pAll)[0];
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// lets get a Map of names to series
QMap<QString, RideFile::SeriesType> snames;
foreach(RideFile::SeriesType s, RideFileCache::meanMaxList()) {
snames.insert(RideFile::seriesName(s, true), s);
}
// extract as QStrings
QList<RideFile::SeriesType> series;
pSeries = Rf_coerceVector(pSeries, STRSXP);
for(int i=0; i <Rf_length(pSeries); i++) {
QString name = CHAR(STRING_ELT(pSeries, i));
RideFile::SeriesType stype;
if ((stype=snames.value(name, RideFile::none)) == RideFile::none) {
Rf_error("Invalid mean maximal series passed to GC.season.peaks.");
return Rf_allocVector(INTSXP, 0);
} else {
series << stype;
}
}
// extract as integers
QList<int>durations;
pDuration = Rf_coerceVector(pDuration, REALSXP);
for(int i=0; i<Rf_length(pDuration); i++) {
durations << REAL(pDuration)[i];
}
// want a list of compares not a dataframe
if (compare && rtool->context) {
// only return compares if its actually active
if (rtool->context->isCompareDateRanges) {
// how many to return?
int count=0;
foreach(CompareDateRange p, rtool->context->compareDateRanges) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int index=0;
// a named list with data.frame 'metrics' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("peaks"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareDateRange p, rtool->context->compareDateRanges) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForDateRangePeaks(all, DateRange(p.start, p.end), pFilter, series, durations);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, index++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else { // compare isn't active...
// otherwise return the current metrics in a compare list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("peaks"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of metrics and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the metrics
DateRange range = rtool->context->currentDateRange();
SEXP df = rtool->dfForDateRangePeaks(all, range, pFilter, series, durations);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (rtool->context && rtool->context->athlete && rtool->context->athlete->rideCache) {
// just a datafram of metrics
DateRange range = rtool->context->currentDateRange();
return rtool->dfForDateRangePeaks(all, range, pFilter, series, durations);
}
// fail
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::dfForDateRangePeaks(bool all, DateRange range, SEXP filter, QList<RideFile::SeriesType> series, QList<int> durations)
{
// so how many vectors in the frame ? +1 is the datetime of the peak
int listsize=series.count() * durations.count() + 1;
SEXP df;
PROTECT(df=Rf_allocVector(VECSXP, listsize));
int dfindex=0;
// and each one needs a name
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, listsize));
SET_STRING_ELT(names, 0, Rf_mkChar("time"));
int next=1;
// how many rides ?
Specification specification;
FilterSet fs;
fs.addFilter(rtool->context->isfiltered, rtool->context->filters);
fs.addFilter(rtool->context->ishomefiltered, rtool->context->homeFilters);
fs.addFilter(rtool->perspective->isFiltered(), rtool->perspective->filterlist(range));
specification.setFilterSet(fs);
// did call contain any filters?
PROTECT(filter=Rf_coerceVector(filter, STRSXP));
for(int i=0; i<Rf_length(filter); i++) {
// if not empty write a filter
QString f(CHAR(STRING_ELT(filter,i)));
if (f != "") {
DataFilter dataFilter(rtool->canvas, rtool->context);
QStringList files;
dataFilter.parseFilter(rtool->context, f, &files);
fs.addFilter(true, files);
}
}
specification.setFilterSet(fs);
UNPROTECT(1);
// how many pass?
int size=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
// apply filters
if (!specification.pass(item)) continue;
// do we want this one ?
if (all || range.pass(item->dateTime.date())) size++;
}
// dates first
SEXP dates;
PROTECT(dates=Rf_allocVector(REALSXP, size));
// fill with values for date and class
int i=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
// apply filters
if (!specification.pass(item)) continue;
if (all || range.pass(item->dateTime.date())) {
REAL(dates)[i++] = item->dateTime.toUTC().toSecsSinceEpoch();
}
}
// POSIXct class
SEXP clas;
PROTECT(clas=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(dates,clas);
Rf_setAttrib(dates, Rf_install("tzone"), Rf_mkString("UTC"));
SET_VECTOR_ELT(df, dfindex++, dates);
foreach(RideFile::SeriesType pseries, series) {
foreach(int pduration, durations) {
// create a vector
SEXP vector;
PROTECT(vector=Rf_allocVector(REALSXP, size));
// give it a name
QString name = QString("peak_%1_%2").arg(RideFile::seriesName(pseries, true)).arg(pduration);
SET_STRING_ELT(names, next++, Rf_mkChar(name.toLatin1().constData()));
// fill with values
// get the value for the series and duration requested, although this is called
int index=0;
foreach(RideItem *item, rtool->context->athlete->rideCache->rides()) {
// apply filters
if (!specification.pass(item)) continue;
// do we want this one ?
if (all || range.pass(item->dateTime.date())) {
// for each series/duration independently its pretty quick since it lseeks to
// the actual value, so /should't/ be too expensive.........
REAL(vector)[index++] = RideFileCache::best(item->context, item->fileName, pseries, pduration);
}
}
// add named vector to the list
SET_VECTOR_ELT(df, dfindex++, vector);
UNPROTECT(1);
}
}
// set names + data.frame
SEXP rownames;
PROTECT(rownames = Rf_allocVector(STRSXP, size));
for(int i=0; i<size; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
Rf_setAttrib(df, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(df, R_RowNamesSymbol, rownames);
Rf_namesgets(df, names);
// df + names + dates + clas + rownames
UNPROTECT(5);
return df;
}
SEXP
RTool::seasonMeanmax(SEXP pAll, SEXP pFilter, SEXP pCompare)
{
// p1 - all=TRUE|FALSE - return all metrics or just within
// the currently selected date range
pAll = Rf_coerceVector(pAll, LGLSXP);
bool all = LOGICAL(pAll)[0];
// p2 - all=TRUE|FALSE - return list of compares (or current if not active)
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// want a list of compares not a dataframe
if (compare && rtool->context) {
// only return compares if its actually active
if (rtool->context->isCompareDateRanges) {
// how many to return?
int count=0;
foreach(CompareDateRange p, rtool->context->compareDateRanges) if (p.isChecked()) count++;
// cool we can return a list of meanaxes to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int lindex=0;
// a named list with data.frame 'metrics' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("meanmax"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareDateRange p, rtool->context->compareDateRanges) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForDateRangeMeanmax(all, DateRange(p.start, p.end), pFilter);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, lindex++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else { // compare isn't active...
// otherwise return the current season meanmax in a compare list
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("meanmax"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of metrics and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the meanmaxes
DateRange range = rtool->context->currentDateRange();
SEXP df = rtool->dfForDateRangeMeanmax(all, range, pFilter);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (rtool->context && rtool->context->athlete && rtool->context->athlete->rideCache) {
// just a datafram of meanmax
DateRange range = rtool->context->currentDateRange();
return rtool->dfForDateRangeMeanmax(all, range, pFilter);
}
// fail
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::activityMeanmax(SEXP pCompare)
{
// a dataframe to return
SEXP ans=NULL;
// p1 - compare=TRUE|FALSE - return list of compare rides if active, or just current
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// return a list
if (compare && rtool->context) {
if (rtool->context->isCompareIntervals) {
// how many to return?
int count=0;
foreach(CompareInterval p, rtool->context->compareIntervals) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int lindex=0;
// a named list with data.frame 'activity' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("meanmax"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareInterval p, rtool->context->compareIntervals) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForActivityMeanmax(p.rideItem);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, lindex++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else if(rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// just return a list of one ride
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("meanmax"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of activity and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForActivityMeanmax(rtool->context->currentRideItem());
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (!compare) { // not compare, so just return a dataframe
// access via global as this is a static function
if(rtool->context && rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// get as a data frame
ans = rtool->dfForActivityMeanmax(rtool->context->currentRideItem());
return ans;
}
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::activityMetrics(SEXP pCompare)
{
// a dataframe to return
SEXP ans=NULL;
// p1 - compare=TRUE|FALSE - return list of compare rides if active, or just current
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// return a list
if (compare && rtool->context) {
if (rtool->context->isCompareIntervals) {
// how many to return?
int count=0;
foreach(CompareInterval p, rtool->context->compareIntervals) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int lindex=0;
// a named list with data.frame 'activity' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("metrics"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareInterval p, rtool->context->compareIntervals) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForRideItem(p.rideItem);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, lindex++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else if(rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// just return a list of one ride
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("metrics"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of activity and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForRideItem(rtool->context->currentRideItem());
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (!compare) { // not compare, so just return a dataframe
// access via global as this is a static function
if(rtool->context && rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// get as a data frame
ans = rtool->dfForRideItem(rtool->context->currentRideItem());
return ans;
}
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::pmc(SEXP pAll, SEXP pMetric, SEXP pType)
{
// parse parameters
// p1 - all=TRUE|FALSE - return all metrics or just within
// the currently selected date range
pAll = Rf_coerceVector(pAll, LGLSXP);
bool all = LOGICAL(pAll)[0];
// p2 - metric="BikeStress" - base stress metric
pMetric = Rf_coerceVector(pMetric, STRSXP);
QString metric (CHAR(STRING_ELT(pMetric,0)));
// p3 - type="Actual" - PMC type: Actual/Planned/Expected
pMetric = Rf_coerceVector(pType, STRSXP);
QString type (CHAR(STRING_ELT(pType,0)));
// return a dataframe with PMC data for all or the current season
// XXX uses the default half-life
if (rtool->context) {
// get the currently selected date range
DateRange range(rtool->context->currentDateRange());
// convert the name to a symbol, if not found just leave as it is
const RideMetricFactory &factory = RideMetricFactory::instance();
for (int i=0; i<factory.metricCount(); i++) {
QString symbol = factory.metricName(i);
QString name = SpecialFields::getInstance().internalName(factory.rideMetric(symbol)->name());
name.replace(" ","_");
if (name == metric) {
metric = symbol;
break;
}
}
// create the data
PMCData pmcData(rtool->context, Specification(), metric);
// how many entries ?
QDate d1970(1970,01,01);
// not unsigned coz date could be configured < 1970 (!)
int from =d1970.daysTo(range.from);
int to =d1970.daysTo(range.to);
unsigned int size = all ? pmcData.days() : (to - from + 1);
// returning a dataframe with
// date, lts, sts, sb, rr
SEXP ans, names;
// date, stress, lts, sts, sb, rr
PROTECT(ans=Rf_allocVector(VECSXP, 6));
// set ther names
PROTECT(names = Rf_allocVector(STRSXP, 6));
SET_STRING_ELT(names, 0, Rf_mkChar("date"));
SET_STRING_ELT(names, 1, Rf_mkChar("stress"));
SET_STRING_ELT(names, 2, Rf_mkChar("lts"));
SET_STRING_ELT(names, 3, Rf_mkChar("sts"));
SET_STRING_ELT(names, 4, Rf_mkChar("sb"));
SET_STRING_ELT(names, 5, Rf_mkChar("rr"));
// DATE - 1 a day from start
SEXP date;
PROTECT(date=Rf_allocVector(INTSXP, size));
unsigned int start = d1970.daysTo(all ? pmcData.start() : range.from);
for(unsigned int k=0; k<size; k++) INTEGER(date)[k] = start + k;
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(date,dclas);
// add to the data.frame
SET_VECTOR_ELT(ans, 0, date);
// PMC DATA
SEXP stress, lts, sts, sb, rr;
PROTECT(stress=Rf_allocVector(REALSXP, size));
PROTECT(lts=Rf_allocVector(REALSXP, size));
PROTECT(sts=Rf_allocVector(REALSXP, size));
PROTECT(sb=Rf_allocVector(REALSXP, size));
PROTECT(rr=Rf_allocVector(REALSXP, size));
int index=0;
if (all) {
// just copy
if (type == "Planned") {
for(unsigned int k=0; k<size; k++) REAL(stress)[k] = pmcData.plannedStress()[k];
for(unsigned int k=0; k<size; k++) REAL(lts)[k] = pmcData.plannedLts()[k];
for(unsigned int k=0; k<size; k++) REAL(sts)[k] = pmcData.plannedSts()[k];
for(unsigned int k=0; k<size; k++) REAL(sb)[k] = pmcData.plannedSb()[k];
for(unsigned int k=0; k<size; k++) REAL(rr)[k] = pmcData.plannedRr()[k];
} else if (type == "Expected") {
for(unsigned int k=0; k<size; k++) REAL(stress)[k] = pmcData.expectedStress()[k];
for(unsigned int k=0; k<size; k++) REAL(lts)[k] = pmcData.expectedLts()[k];
for(unsigned int k=0; k<size; k++) REAL(sts)[k] = pmcData.expectedSts()[k];
for(unsigned int k=0; k<size; k++) REAL(sb)[k] = pmcData.expectedSb()[k];
for(unsigned int k=0; k<size; k++) REAL(rr)[k] = pmcData.expectedRr()[k];
} else {
for(unsigned int k=0; k<size; k++) REAL(stress)[k] = pmcData.stress()[k];
for(unsigned int k=0; k<size; k++) REAL(lts)[k] = pmcData.lts()[k];
for(unsigned int k=0; k<size; k++) REAL(sts)[k] = pmcData.sts()[k];
for(unsigned int k=0; k<size; k++) REAL(sb)[k] = pmcData.sb()[k];
for(unsigned int k=0; k<size; k++) REAL(rr)[k] = pmcData.rr()[k];
}
} else {
int day = d1970.daysTo(pmcData.start());
for(int k=0; k < pmcData.days(); k++) {
// day today
if (day >= from && day <= to) {
if (type == "Planned") {
REAL(stress)[index] = pmcData.plannedStress()[k];
REAL(lts)[index] = pmcData.plannedLts()[k];
REAL(sts)[index] = pmcData.plannedSts()[k];
REAL(sb)[index] = pmcData.plannedSb()[k];
REAL(rr)[index] = pmcData.plannedRr()[k];
} else if (type == "Expected") {
REAL(stress)[index] = pmcData.expectedStress()[k];
REAL(lts)[index] = pmcData.expectedLts()[k];
REAL(sts)[index] = pmcData.expectedSts()[k];
REAL(sb)[index] = pmcData.expectedSb()[k];
REAL(rr)[index] = pmcData.expectedRr()[k];
} else {
REAL(stress)[index] = pmcData.stress()[k];
REAL(lts)[index] = pmcData.lts()[k];
REAL(sts)[index] = pmcData.sts()[k];
REAL(sb)[index] = pmcData.sb()[k];
REAL(rr)[index] = pmcData.rr()[k];
}
index++;
}
day++;
}
}
// add to the list
SET_VECTOR_ELT(ans, 1, stress);
SET_VECTOR_ELT(ans, 2, lts);
SET_VECTOR_ELT(ans, 3, sts);
SET_VECTOR_ELT(ans, 4, sb);
SET_VECTOR_ELT(ans, 5, rr);
SEXP rownames;
PROTECT(rownames = Rf_allocVector(STRSXP, size));
for(unsigned int i=0; i<size; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
UNPROTECT(10);
// return it
return ans;
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::measures(SEXP pAll, SEXP pGroup)
{
// parse parameters
// p1 - all=TRUE|FALSE - return all measures or just within
// the currently selected date range
pAll = Rf_coerceVector(pAll, LGLSXP);
bool all = LOGICAL(pAll)[0];
// p2 - group="Body"|"Hrv" - return list of measures for selected group
pGroup = Rf_coerceVector(pGroup, STRSXP);
QString groupSymbol (CHAR(STRING_ELT(pGroup,0)));
// return a dataframe with Measures data for all or the current season
if (rtool->context) {
// get the currently selected date range
DateRange range(rtool->context->currentDateRange());
// convert the group symbol to an index, default to Body=0
int groupIdx = rtool->context->athlete->measures->getGroupSymbols().indexOf(groupSymbol);
if (groupIdx < 0) groupIdx = 0;
// Update range for all
if (all) {
range.from = rtool->context->athlete->measures->getStartDate(groupIdx);
range.to = rtool->context->athlete->measures->getEndDate(groupIdx);
}
// how many entries ?
QDate d1970(1970,01,01);
// not unsigned coz date could be configured < 1970 (!)
int from =d1970.daysTo(range.from);
int to =d1970.daysTo(range.to);
unsigned int size = to - from + 1;
// returning a dataframe with
// date, field1, field2, ...
SEXP ans, names;
QStringList fieldSymbols = rtool->context->athlete->measures->getFieldSymbols(groupIdx);
// date, field1, field2, ...
PROTECT(ans=Rf_allocVector(VECSXP, fieldSymbols.count() + 1));
// set the names
PROTECT(names = Rf_allocVector(STRSXP, fieldSymbols.count() + 1));
SET_STRING_ELT(names, 0, Rf_mkChar("date"));
for (int i=0; i<fieldSymbols.count(); i++)
SET_STRING_ELT(names, i + 1, Rf_mkChar(fieldSymbols[i].toLatin1().constData()));
// DATE - 1 a day from start
SEXP date;
PROTECT(date=Rf_allocVector(INTSXP, size));
unsigned int start = d1970.daysTo(range.from);
for(unsigned int k=0; k<size; k++) INTEGER(date)[k] = start + k;
SEXP dclas;
PROTECT(dclas=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(dclas, 0, Rf_mkChar("Date"));
Rf_classgets(date,dclas);
// add to the data.frame
SET_VECTOR_ELT(ans, 0, date);
// MEASURES DATA
QVector<SEXP> fields(fieldSymbols.count());
for (int i=0; i<fieldSymbols.count(); i++)
PROTECT(fields[i]=Rf_allocVector(REALSXP, size));
int index=0;
int day = from;
for(unsigned int k=0; k < size; k++) {
// day today
if (day >= from && day <= to) {
for (int fieldIdx=0; fieldIdx<fields.count(); fieldIdx++)
REAL(fields[fieldIdx])[index] = rtool->context->athlete->measures->getFieldValue(groupIdx, d1970.addDays(day), fieldIdx);
index++;
}
day++;
}
// add to the list
for (int fieldIdx=0; fieldIdx<fields.count(); fieldIdx++)
SET_VECTOR_ELT(ans, fieldIdx+1, fields[fieldIdx]);
SEXP rownames;
PROTECT(rownames = Rf_allocVector(STRSXP, size));
for(unsigned int i=0; i<size; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_namesgets(ans, names);
UNPROTECT(5 + fieldSymbols.count());
// return it
return ans;
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::activityWBal(SEXP pCompare)
{
SEXP ans=NULL;
// p1 - compare=TRUE|FALSE - return list of compare rides if active, or just current
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// return a list
if (compare && rtool->context) {
if (rtool->context->isCompareIntervals) {
// how many to return?
int count=0;
foreach(CompareInterval p, rtool->context->compareIntervals) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int lindex=0;
// a named list with data.frame 'activity' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("activity"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareInterval p, rtool->context->compareIntervals) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForActivityWBal(p.rideItem->ride());
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, lindex++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else if(rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// just return a list of one ride
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("activity"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of activity and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
RideFile *f = const_cast<RideItem*>(rtool->context->currentRideItem())->ride();
f->recalculateDerivedSeries();
SEXP df = rtool->dfForActivityWBal(f);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (!compare) { // not compare, so just return a dataframe
// access via global as this is a static function
if(rtool->context && rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// get the ride
RideFile *f = const_cast<RideItem*>(rtool->context->currentRideItem())->ride();
f->recalculateDerivedSeries();
// get as a data frame
ans = rtool->dfForActivityWBal(f);
return ans;
}
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::dfForActivityWBal(RideFile*f)
{
// return a data frame with wpbal in 1s samples
if(f && f->wprimeData()) {
// get as a data frame
WPrime *w = f->wprimeData();
if (w && w->ydata().count() >0) {
// construct a vector
SEXP ans;
PROTECT(ans=Rf_allocVector(REALSXP, w->ydata().count()));
// add values
for(int i=0; i<w->ydata().count(); i++) REAL(ans)[i] = w->ydata()[i];
UNPROTECT(1);
return ans;
}
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::activityXData(SEXP pName, SEXP pCompare)
{
SEXP ans=NULL;
// p1 - compare=TRUE|FALSE - return list of compare rides if active, or just current
pCompare = Rf_coerceVector(pCompare, LGLSXP);
bool compare = LOGICAL(pCompare)[0];
// p2 - name="" - XData series name
pName = Rf_coerceVector(pName, STRSXP);
QString name (CHAR(STRING_ELT(pName,0)));
// return a list
if (compare && rtool->context) {
if (rtool->context->isCompareIntervals) {
// how many to return?
int count=0;
foreach(CompareInterval p, rtool->context->compareIntervals) if (p.isChecked()) count++;
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, count));
int lindex=0;
// a named list with data.frame 'activity' and color 'color'
SEXP namedlist;
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("activity"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// create a data.frame for each and add to list
foreach(CompareInterval p, rtool->context->compareIntervals) {
if (p.isChecked()) {
// create a named list
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
SEXP df = rtool->dfForActivityXData(p.rideItem->ride(), name);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar(p.color.name().toLatin1().constData()));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, lindex++, namedlist);
UNPROTECT(2);
}
}
UNPROTECT(2); // list and names
return list;
} else if(rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// just return a list of one ride
// cool we can return a list of intervals to compare
SEXP list;
PROTECT(list=Rf_allocVector(VECSXP, 1));
// names
SEXP names;
PROTECT(names=Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(names, 0, Rf_mkChar("activity"));
SET_STRING_ELT(names, 1, Rf_mkChar("color"));
// named list of activity and color
SEXP namedlist;
PROTECT(namedlist=Rf_allocVector(VECSXP, 2));
// add the ride
RideFile *f = const_cast<RideItem*>(rtool->context->currentRideItem())->ride();
SEXP df = rtool->dfForActivityXData(f, name);
SET_VECTOR_ELT(namedlist, 0, df);
// add the color
SEXP color;
PROTECT(color=Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(color, 0, Rf_mkChar("#FF00FF"));
SET_VECTOR_ELT(namedlist, 1, color);
// name them
Rf_namesgets(namedlist, names);
// add to back and move on
SET_VECTOR_ELT(list, 0, namedlist);
UNPROTECT(4);
return list;
}
} else if (!compare) { // not compare, so just return a dataframe
// access via global as this is a static function
if(rtool->context && rtool->context->currentRideItem() && const_cast<RideItem*>(rtool->context->currentRideItem())->ride()) {
// get the ride
RideFile *f = const_cast<RideItem*>(rtool->context->currentRideItem())->ride();
// get as a data frame
ans = rtool->dfForActivityXData(f, name);
return ans;
}
}
// nothing to return
return Rf_allocVector(INTSXP, 0);
}
SEXP
RTool::dfForActivityXData(RideFile*f, QString name)
{
// When no name is given, return names present as a list
if (name.isEmpty()) {
SEXP names;
PROTECT(names = Rf_allocVector(STRSXP, f->xdata().count()));
int k = 0;
foreach(name, f->xdata().keys()) SET_STRING_ELT(names, k++, Rf_mkChar(name.toLatin1().constData()));
UNPROTECT(1);
return names;
}
// nothing to return if XData series is not present
if (!f->xdata().contains(name)) return Rf_allocVector(INTSXP, 0);
XDataSeries* xds = f->xdata()[name];
// PROTECT count
int pcount = 0;
// how many series?
int seriescount = xds->valuename.count();
// how many data points?
int points = xds->datapoints.count();
// if we have any series we will continue and add 'time' and 'distance' series
if (seriescount) seriescount += 2;
else return Rf_allocVector(INTSXP, 0);
// we return a list of series vectors
SEXP ans = PROTECT(Rf_allocVector(VECSXP, seriescount));
pcount++;
// we collect the names as we go
SEXP names = PROTECT(Rf_allocVector(STRSXP, seriescount)); // names attribute (column names)
pcount++;
int next = 0;
// TIME
// add in actual time in POSIXct format
SEXP time = PROTECT(Rf_allocVector(REALSXP, points));
pcount++;
// fill with values for date and class
for(int k=0; k<points; k++) REAL(time)[k] = f->startTime().addSecs(xds->datapoints[k]->secs).toUTC().toSecsSinceEpoch();
// POSIXct class
SEXP clas = PROTECT(Rf_allocVector(STRSXP, 2));
pcount++;
SET_STRING_ELT(clas, 0, Rf_mkChar("POSIXct"));
SET_STRING_ELT(clas, 1, Rf_mkChar("POSIXt"));
Rf_classgets(time,clas);
// we use "UTC" for all timezone
Rf_setAttrib(time, Rf_install("tzone"), Rf_mkString("UTC"));
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, time);
SET_STRING_ELT(names, next++, Rf_mkChar("time"));
// DISTANCE
// set a vector
SEXP distance = PROTECT(Rf_allocVector(REALSXP, points));
pcount++;
// fill with values
for(int k=0; k<points; k++) REAL(distance)[k] = xds->datapoints[k]->km;
// add to the data.frame and give it a name
SET_VECTOR_ELT(ans, next, distance);
SET_STRING_ELT(names, next++, Rf_mkChar("distance"));
// add a series for every one
int valueIdx = 0;
foreach(QString series, xds->valuename) {
// set a vector
SEXP vector = PROTECT(Rf_allocVector(REALSXP, points));
pcount++;
int idx = 0;
foreach(XDataPoint* p, xds->datapoints) {
double val = p->number[valueIdx];
REAL(vector)[idx++] = (val == RideFile::NA) ? NA_REAL : val;
}
// add to the list
SET_VECTOR_ELT(ans, next, vector);
// give it a name
SET_STRING_ELT(names, next++, Rf_mkChar(QString("%1_%2").arg(xds->name).arg(series.replace(" ","_")).toLatin1().constData()));
valueIdx++;
}
// add rownames
SEXP rownames = PROTECT(Rf_allocVector(STRSXP, points));
pcount++;
for(int i=0; i<points; i++) {
QString rownumber=QString("%1").arg(i+1);
SET_STRING_ELT(rownames, i, Rf_mkChar(rownumber.toLatin1().constData()));
}
// turn the list into a data frame + set column names
Rf_setAttrib(ans, R_RowNamesSymbol, rownames);
Rf_setAttrib(ans, R_ClassSymbol, Rf_mkString("data.frame"));
Rf_namesgets(ans, names);
UNPROTECT(pcount);
// return a valid result
return ans;
}
//
// Working with Generic Charts
//
SEXP
RTool::setChart(SEXP title, SEXP type, SEXP animate, SEXP legpos, SEXP stack, SEXP orientation)
{
if (rtool == NULL || rtool->context == NULL || rtool->chart == NULL) return Rf_allocVector(INTSXP, 0);
GenericChartInfo info;
// title
PROTECT(title=Rf_coerceVector(title, STRSXP));
info.title=QString(CHAR(STRING_ELT(title,0)));
UNPROTECT(1);
// type
PROTECT(type=Rf_coerceVector(type,INTSXP));
info.type=INTEGER(type)[0];
UNPROTECT(1);
// animation
animate = Rf_coerceVector(animate, LGLSXP);
info.animate = LOGICAL(animate)[0];
// legend position
PROTECT(legpos=Rf_coerceVector(legpos,INTSXP));
info.legendpos=INTEGER(legpos)[0];
UNPROTECT(1);
// stack
stack = Rf_coerceVector(stack, LGLSXP);
info.stack = LOGICAL(stack)[0];
// type
PROTECT(orientation=Rf_coerceVector(orientation,INTSXP));
info.orientation=INTEGER(orientation)[0];
UNPROTECT(1);
// call generic chart
rtool->chart->chart->initialiseChart(info.title, info.type, info.animate, info.legendpos, info.stack, info.orientation);
// return 0
return Rf_allocVector(INTSXP,0);
}
SEXP
RTool::addCurve(SEXP name, SEXP xseries, SEXP yseries, SEXP fseries, SEXP xname, SEXP yname, SEXP labels, SEXP colors,
SEXP line, SEXP symbol, SEXP size, SEXP color, SEXP opacity, SEXP opengl, SEXP legend, SEXP datalabels, SEXP fill)
{
Q_UNUSED(labels) //XXX todo
Q_UNUSED(colors) //XXX todo
if (rtool == NULL || rtool->context == NULL || rtool->chart == NULL) return Rf_allocVector(INTSXP, 0);
GenericSeriesInfo info;
// name
PROTECT(name=Rf_coerceVector(name, STRSXP));
info.name=QString(CHAR(STRING_ELT(name,0)));
UNPROTECT(1);
// xseries
PROTECT(xseries=Rf_coerceVector(xseries,REALSXP));
long vs=Rf_length(xseries);
info.xseries.resize(vs);
for(int i=0; i<vs; i++) info.xseries[i]=REAL(xseries)[i];
UNPROTECT(1);
// yseries
PROTECT(yseries=Rf_coerceVector(yseries,REALSXP));
vs=Rf_length(yseries);
info.yseries.resize(vs);
for(int i=0; i<vs; i++) info.yseries[i]=REAL(yseries)[i];
UNPROTECT(1);
// fseries
PROTECT(fseries=Rf_coerceVector(fseries,STRSXP));
vs=Rf_length(fseries);
info.fseries.resize(vs);
for(int i=0; i<vs; i++) info.fseries[i] = QString(CHAR(STRING_ELT(fseries,i)));
UNPROTECT(1);
// yname
PROTECT(yname=Rf_coerceVector(yname, STRSXP));
info.yname=QString(CHAR(STRING_ELT(yname,0)));
UNPROTECT(1);
// xname
PROTECT(xname=Rf_coerceVector(xname, STRSXP));
info.xname=QString(CHAR(STRING_ELT(xname,0)));
UNPROTECT(1);
// labels
// XXX todo
// colors
// XXX todo
// line
PROTECT(line=Rf_coerceVector(line,INTSXP));
info.line=INTEGER(line)[0];
UNPROTECT(1);
// symbol
PROTECT(symbol=Rf_coerceVector(symbol,INTSXP));
info.symbol=INTEGER(symbol)[0];
UNPROTECT(1);
// size
PROTECT(size=Rf_coerceVector(size,REALSXP));
info.size=REAL(size)[0];
UNPROTECT(1);
// color
PROTECT(color=Rf_coerceVector(color, STRSXP));
info.color=QString(CHAR(STRING_ELT(color,0)));
UNPROTECT(1);
// opacity
PROTECT(opacity=Rf_coerceVector(opacity,INTSXP));
info.opacity=INTEGER(opacity)[0];
UNPROTECT(1);
// opengl
opengl = Rf_coerceVector(opengl, LGLSXP);
info.opengl = LOGICAL(opengl)[0];
// legend
legend = Rf_coerceVector(legend, LGLSXP);
info.legend = LOGICAL(legend)[0];
// legend
datalabels = Rf_coerceVector(datalabels, LGLSXP);
info.datalabels = LOGICAL(datalabels)[0];
// fill
fill = Rf_coerceVector(fill, LGLSXP);
info.fill = LOGICAL(fill)[0];
// add to chart
rtool->chart->chart->addCurve(info.name, info.xseries, info.yseries, info.fseries, info.xname, info.yname, info.labels, info.colors, info.line,
info.symbol, info.size, info.color, info.opacity, info.opengl, info.legend, info.datalabels, info.fill, info.aggregateby, info.annotations);
// return 0
return Rf_allocVector(INTSXP,0);
}
SEXP
RTool::configureAxis(SEXP name, SEXP visible, SEXP align, SEXP min, SEXP max,
SEXP type, SEXP labelcolor, SEXP axiscolor, SEXP log, SEXP categories)
{
Q_UNUSED(align) // we always pass -1 for now
Q_UNUSED(categories) // XXX TODO
if (rtool == NULL || rtool->context == NULL || rtool->chart == NULL) return Rf_allocVector(INTSXP, 0);
GenericAxisInfo info;
// name
PROTECT(name=Rf_coerceVector(name, STRSXP));
info.name=QString(CHAR(STRING_ELT(name,0)));
UNPROTECT(1);
// visible
visible = Rf_coerceVector(visible, LGLSXP);
info.visible = LOGICAL(visible)[0];
// align- ignore !
// min
PROTECT(min=Rf_coerceVector(min,REALSXP));
info.minx=REAL(min)[0];
UNPROTECT(1);
// max
PROTECT(max=Rf_coerceVector(max,REALSXP));
info.maxx=REAL(max)[0];
UNPROTECT(1);
// type
PROTECT(type=Rf_coerceVector(type,INTSXP));
info.type=static_cast<GenericAxisInfo::AxisInfoType>(INTEGER(type)[0]);
UNPROTECT(1);
// labelcolor string
PROTECT(labelcolor=Rf_coerceVector(labelcolor, STRSXP));
info.labelcolor=QString(CHAR(STRING_ELT(labelcolor,0)));
UNPROTECT(1);
// color string
PROTECT(axiscolor=Rf_coerceVector(axiscolor, STRSXP));
info.axiscolor=QString(CHAR(STRING_ELT(axiscolor,0)));
UNPROTECT(1);
// log scale
log = Rf_coerceVector(log, LGLSXP);
info.log = LOGICAL(log)[0];
// categories
//XXX todo
// add to chart -- XXX need to think on how to set axis colors -- or if we even should allow it
rtool->chart->chart->configureAxis(info.name, info.visible, -1 /*info.align*/, info.minx, info.maxx, info.type, "" /*info.labelcolor.name()*/, ""/*info.axiscolor.name()*/, info.log, info.categories);
// return 0
return Rf_allocVector(INTSXP,0);
}
SEXP
RTool::annotate(SEXP type, SEXP p1, SEXP p2, SEXP p3)
{
if (rtool == NULL || rtool->context == NULL || rtool->chart == NULL) return Rf_allocVector(INTSXP, 0);
// type
PROTECT(type=Rf_coerceVector(type, STRSXP));
QString atype=QString(CHAR(STRING_ELT(type,0)));
UNPROTECT(1);
if (atype == "label") {
Q_UNUSED(p3);
// p1 is the series name
PROTECT(p1=Rf_coerceVector(p1, STRSXP));
QString series=QString(CHAR(STRING_ELT(p1,0)));
UNPROTECT(1);
// p2 is a list of strings
PROTECT(p2=Rf_coerceVector(p2, STRSXP));
QStringList list;
for(int i=0; i<Rf_length(p2); i++) {
// if not empty write a filter
QString f(CHAR(STRING_ELT(p2,i)));
list << f;
}
UNPROTECT(1);
// XXX todo fix up or deprecate ???? if (list.count() > 0) rtool->chart->chart->annotateLabel(series, list);
}
// return 0
return Rf_allocVector(INTSXP,0);
}