Add QT Charts to R Chart

.. mostly complete, following the Python charts lead on the
   API and basic UX.

.. some nits left in there; pie/bar chart categories and labels
   and an open question on whether/how we let users set the axis
   color / leave it alone.
This commit is contained in:
Mark Liversedge
2020-03-09 19:40:17 +00:00
parent 9e7f843d43
commit 35a37ff846
4 changed files with 288 additions and 3 deletions

View File

@@ -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<int> 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);

View File

@@ -28,6 +28,7 @@
#include <QCheckBox>
#include <QSplitter>
#include <QByteArray>
#include <QStackedWidget>
#include <string.h>
#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;

View File

@@ -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; 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);
// 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];
// add to chart
rtool->chart->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<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);
}

View File

@@ -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;