diff --git a/src/Charts/RChart.cpp b/src/Charts/RChart.cpp index 7d31195f5..473e3052a 100644 --- a/src/Charts/RChart.cpp +++ b/src/Charts/RChart.cpp @@ -22,6 +22,7 @@ #include "Colors.h" #include "TabView.h" +#include "GenericChart.h" // unique identifier for each chart static int id=0; @@ -275,7 +276,14 @@ void RConsole::contextMenuEvent(QContextMenuEvent *e) RChart::RChart(Context *context, bool ridesummary) : GcChartWindow(context), context(context), ridesummary(ridesummary) { - setControls(NULL); + // settings - just choosing output type + plotOnChartSetting = new QCheckBox(tr("Use GC charts"), this); + plotOnChartSetting->setChecked(false);// by default we use R graphics device (legacy charts) + QWidget *c=new QWidget(this); + QVBoxLayout *cl = new QVBoxLayout(c); + cl->addWidget(plotOnChartSetting); + cl->addStretch(); + setControls(c); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->setSpacing(0); @@ -338,7 +346,16 @@ RChart::RChart(Context *context, bool ridesummary) : GcChartWindow(context), con canvas = new RCanvas(context, this); canvas->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - splitter->addWidget(canvas); + + chart = new GenericChart(this, context); + chart->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + + stack = new QStackedWidget(this); + stack->addWidget(canvas); + stack->addWidget(chart); + stack->setCurrentIndex(0); + + splitter->addWidget(stack); // make splitter reasonable QList sizes; @@ -371,6 +388,9 @@ RChart::RChart(Context *context, bool ridesummary) : GcChartWindow(context), con // reveal controls connect(showCon, SIGNAL(stateChanged(int)), this, SLOT(showConChanged(int))); + // output to graphics device or qt chart + connect(plotOnChartSetting, SIGNAL(stateChanged(int)), this, SLOT(plotOnChartChanged())); + // config changes connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32))); configChanged(CONFIG_APPEARANCE); @@ -393,6 +413,27 @@ RChart::RChart(Context *context, bool ridesummary) : GcChartWindow(context), con } } +bool +RChart::plotOnChart() const +{ + return plotOnChartSetting->isChecked(); +} + +void +RChart::setPlotOnChart(bool x) +{ + plotOnChartSetting->setChecked(x); +} + +void +RChart::plotOnChartChanged() +{ + + // now deal with that by adjusting which is visible. + if (plotOnChartSetting->isChecked()) stack->setCurrentIndex(1); // generic chart + else stack->setCurrentIndex(0); // r graphics device +} + bool RChart::eventFilter(QObject *, QEvent *e) { @@ -536,6 +577,9 @@ RChart::runScript() canvas->newPage(); } + // finalise the chart (even if not on show) + chart->finaliseChart(); + // turn off updates for a sec setUpdatesEnabled(true); diff --git a/src/Charts/RChart.h b/src/Charts/RChart.h index c9b8f02be..bb9a821b0 100644 --- a/src/Charts/RChart.h +++ b/src/Charts/RChart.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "GoldenCheetah.h" @@ -36,6 +37,7 @@ #include "RCanvas.h" class RChart; +class GenericChart; // a console widget to type commands and display response class RConsole : public QTextEdit { @@ -83,6 +85,7 @@ class RChart : public GcChartWindow { Q_PROPERTY(QString script READ getScript WRITE setScript USER true) Q_PROPERTY(QString state READ getState WRITE setState USER true) + Q_PROPERTY(bool plotOnChart READ plotOnChart WRITE setPlotOnChart USER true) Q_PROPERTY(bool showConsole READ showConsole WRITE setConsole USER true) public: @@ -96,6 +99,7 @@ class RChart : public GcChartWindow { QTextEdit *script; RConsole *console; RCanvas *canvas; + GenericChart *chart; bool showConsole() const { return (showCon ? showCon->isChecked() : true); } void setConsole(bool); @@ -106,17 +110,23 @@ class RChart : public GcChartWindow { QString getState() const; void setState(QString); + bool plotOnChart() const; + void setPlotOnChart(bool x); + public slots: void configChanged(qint32); void showConChanged(int state); + void plotOnChartChanged(); void runScript(); protected: // enable stopping long running scripts bool eventFilter(QObject *, QEvent *e); + QStackedWidget *stack; QSplitter *splitter; QSplitter *leftsplitter; + QCheckBox *plotOnChartSetting; private: Context *context; diff --git a/src/R/RTool.cpp b/src/R/RTool.cpp index 0e01d3cf2..9adc09c98 100644 --- a/src/R/RTool.cpp +++ b/src/R/RTool.cpp @@ -36,6 +36,7 @@ #include "Zones.h" #include "HrZones.h" #include "PaceZones.h" +#include "GenericChart.h" // Structure used to register routines has changed in v3.4 of R // @@ -132,6 +133,9 @@ RTool::RTool() { "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 }, { NULL, NULL, 0,0 } }; @@ -156,6 +160,9 @@ RTool::RTool() { "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 }, { NULL, NULL, 0,0,0 } }; @@ -192,6 +199,9 @@ RTool::RTool() { "GC.season.pmc", (DL_FUNC) &RTool::pmc, 2 }, // 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, 13 }, + { "GC.chart.configureAxis", (DL_FUNC) &RTool::configureAxis, 10 }, { NULL, NULL, 0 } }; @@ -290,10 +300,33 @@ RTool::RTool() "GC.metrics <- function(all=FALSE, filter=\"\", compare=FALSE) { .Call(\"GC.season.metrics\", all, filter, compare) }\n" "GC.pmc <- function(all=FALSE, metric=\"BikeStress\") { .Call(\"GC.season.pmc\", all, metric) }\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(), xname=\"xaxis\", yname=\"yaxis\", min=-1, max=-1, labels=c(), colors=c(), line=1,symbol=0,size=2,color=\"red\",opacity=100,opengl=TRUE) { .Call(\"GC.chart.addCurve\", name, xseries, yseries, xname, yname, labels, colors, line, symbol, size, color, opacity, opengl)}\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" + + // 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.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.CHART.LINE<-1\n" + "GC.CHART.SCATTER<-2\n" + "GC.CHART.BAR<-3\n" + "GC.CHART.PIE<-4\n" + // version and build "GC.version <- function() { return(\"%1\") }\n" "GC.build <- function() { return(%2) }\n" - "par.default <- par()\n") + "par.default <- par()\n" + ) .arg(VERSION_STRING) .arg(VERSION_LATEST) .arg("https://cloud.r-project.org/")); @@ -3950,3 +3983,194 @@ RTool::dfForActivityXData(RideFile*f, QString name) // 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 xname, SEXP yname, SEXP labels, SEXP colors, + SEXP line, SEXP symbol, SEXP size, SEXP color, SEXP opacity, SEXP opengl) +{ + 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; ichart->chart->addCurve(info.name, info.xseries, info.yseries, info.xname, info.yname, info.labels, info.colors, info.line, info.symbol, info.size, info.color, info.opacity, info.opengl); + + // 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 + + fprintf(stderr, "configure axis...\n"); fflush(stderr); + + 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(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); +} diff --git a/src/R/RTool.h b/src/R/RTool.h index a54732770..9dbf57140 100644 --- a/src/R/RTool.h +++ b/src/R/RTool.h @@ -69,6 +69,13 @@ class RTool { static SEXP pmc(SEXP all, SEXP metric); static SEXP measures(SEXP all, SEXP group); + // charts + static SEXP setChart(SEXP title, SEXP type, SEXP animate, SEXP legpos, SEXP stack, SEXP orientation); + static SEXP addCurve(SEXP name, SEXP xseries, SEXP yseries, SEXP xname, SEXP yname, SEXP labels, SEXP colors, + SEXP line, SEXP symbol, SEXP size, SEXP color, SEXP opacity, SEXP opengl); + static SEXP configureAxis(SEXP name, SEXP visible, SEXP align, SEXP min, SEXP max, + SEXP type, SEXP labelcolor, SEXP color, SEXP log, SEXP categories); + bool starting; bool failed; bool cancelled;