R Canvas with Basic Primitives

.. not complete, but we now have a canvas (QGraphicsView)
   to plot the R output without needing to use x11() or
   quartz(), window() etc.

.. the primitives do not honour the graphic engine context so
   all lines etc are white on black.

.. will fix and improve in followup commits, need to test
   with QT4.8 and cross-platform.
This commit is contained in:
Mark Liversedge
2016-04-20 14:04:32 +01:00
parent cba05ce7a1
commit 39b63223cd
9 changed files with 205 additions and 117 deletions

102
src/Charts/RCanvas.cpp Normal file
View File

@@ -0,0 +1,102 @@
/*
* 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 "RCanvas.h"
#include "Context.h"
#include "Colors.h"
RCanvas::RCanvas(Context *context, QWidget *parent) : QGraphicsView(parent), context(context)
{
// no frame, its ugly
setFrameStyle(QFrame::NoFrame);
// add a scene
scene = new QGraphicsScene(this);
this->setScene(scene);
// flip horizontal as y-co-ordinates place the
// origin in the bottom left, not screen co-ords
// of top left. x axis is fine though.
scale(1,-1);
// set colors etc
configChanged(CONFIG_APPEARANCE);
// connect
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
}
void
RCanvas::configChanged(qint32)
{
// set background etc to the prevailing defaults
QPalette p = palette();
p.setColor(QPalette::Base, GColor(CPLOTBACKGROUND));
p.setColor(QPalette::Text, GCColor::invertColor(GColor(CPLOTBACKGROUND)));
setPalette(p);
}
void
RCanvas::newPage()
{
scene->clear();
}
void
RCanvas::circle(double x, double y, double r, QPen p, QBrush b)
{
QGraphicsEllipseItem *c=new QGraphicsEllipseItem(x,y,r+r,r+r);
c->setPen(p);
c->setBrush(b);
scene->addItem(c);
}
void
RCanvas::line(double x1, double y1, double x2, double y2, QPen p)
{
QGraphicsLineItem *l=new QGraphicsLineItem(x1,y1,x2,y2);
l->setPen(p);
scene->addItem(l);
}
void
RCanvas::polyline(int n, double *x, double *y, QPen p)
{
// origin
double x0=x[0], y0=y[0];
// create a line per path
for(int i=1; i<n;i++) {
double x1=x[i], y1=y[i];
line(x0,y0,x1,y1,p);
x0=x1;
y0=y1;
}
}
void
RCanvas::rectangle(double x0, double y0, double x1, double y1,QPen p, QBrush b)
{
QGraphicsRectItem *r=new QGraphicsRectItem(x0,y0,x1-x0,y1-y0);
r->setPen(p);
r->setBrush(b);
scene->addItem(r);
}

56
src/Charts/RCanvas.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* 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
*/
#ifndef _GC_RCanvas_h
#define _GC_RCanvas_h 1
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGraphicsEllipseItem>
class Context;
// the chart
class RCanvas : public QGraphicsView {
Q_OBJECT
public:
RCanvas(Context *, QWidget *parent);
public slots:
void configChanged(qint32);
// graphic events
void newPage();
void circle(double x, double y, double r, QPen, QBrush);
void line(double x1, double y1, double x2, double y2, QPen);
void polyline(int n, double *x, double *y, QPen);
void rectangle(double x0, double y0, double x1, double y1,QPen,QBrush);
protected:
QGraphicsScene *scene;
private:
QWidget *parent;
Context *context;
};
#endif

View File

@@ -25,9 +25,9 @@
// unique identifier for each chart
static int id=0;
RConsole::RConsole(Context *context, QWidget *parent)
RConsole::RConsole(Context *context, RChart *parent)
: QTextEdit(parent)
, context(context), localEchoEnabled(true)
, context(context), localEchoEnabled(true), parent(parent)
{
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
setFrameStyle(QFrame::NoFrame);
@@ -138,6 +138,8 @@ void RConsole::keyPressEvent(QKeyEvent *e)
// set the context for the call - used by all the
// R functions to access the athlete data/model etc
rtool->context = context;
rtool->canvas = parent->canvas;
(*rtool->R)["GC.athlete"] = context->athlete->cyclist.toStdString();
(*rtool->R)["GC.athlete.home"] = context->athlete->home->root().absolutePath().toStdString();
@@ -174,7 +176,7 @@ void RConsole::keyPressEvent(QKeyEvent *e)
// clear context
rtool->context = NULL;
rtool->canvas = NULL;
}
// next prompt
@@ -243,14 +245,11 @@ RChart::RChart(Context *context) : GcChartWindow(context)
console = new RConsole(context, this);
splitter->addWidget(console);
QWidget *surface = new QSvgWidget(this);
surface->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
splitter->addWidget(surface);
QPalette p = palette();
p.setColor(QPalette::Base, GColor(CPLOTBACKGROUND));
p.setColor(QPalette::Text, GCColor::invertColor(GColor(CPLOTBACKGROUND)));
surface->setPalette(p);
canvas = new RCanvas(context, this);
canvas->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
splitter->addWidget(canvas);
}
}

View File

@@ -26,13 +26,14 @@
#include <QTextEdit>
#include <QScrollBar>
#include <QSplitter>
#include <QSvgWidget>
#include <string.h>
#include "GoldenCheetah.h"
#include "Context.h"
#include "Athlete.h"
#include "RCanvas.h"
class RChart;
// a console widget to type commands and display response
class RConsole : public QTextEdit {
@@ -47,7 +48,7 @@ public slots:
void rMessage(QString);
public:
explicit RConsole(Context *context, QWidget *parent = 0);
explicit RConsole(Context *context, RChart *parent = 0);
void putData(QString data);
void putData(QColor color, QString data);
@@ -70,6 +71,7 @@ protected:
private:
Context *context;
bool localEchoEnabled;
RChart *parent;
};
// the chart
@@ -80,10 +82,12 @@ class RChart : public GcChartWindow {
public:
RChart(Context *context);
// receives all the events
RCanvas *canvas;
protected:
QSplitter *splitter;
RConsole *console;
QSvgWidget *surface;
private:
Context *context;

View File

@@ -32,10 +32,17 @@ RGraphicsDevice::RGraphicsDevice ()
// set the inital graphics device to GC
createGD();
// add GC.display() to create a new device
// once only initialisation
initialize();
}
bool RGraphicsDevice::initialize()
{
// register device creation routine
(*(rtool->R))["GC.display"] = Rcpp::InternalFunction(GCdisplay);
return true;
}
void RGraphicsDevice::NewPage(const pGEcontext gc, pDevDesc dev)
{
qDebug()<<"RGD: NewPage";
@@ -43,10 +50,10 @@ void RGraphicsDevice::NewPage(const pGEcontext gc, pDevDesc dev)
//XXXqDebughandler::newPage(gc, dev);
// fire event (pass previousPageSnapshot)
if (!rtool || !rtool->dev || !rtool->dev->gcGEDevDesc) return;
if (!rtool || !rtool->canvas) return;
// replay snapshot
//SEXP previousPageSnapshot = rtool->dev->gcGEDevDesc->savedSnapshot;
rtool->canvas->newPage();
}
Rboolean RGraphicsDevice::NewFrameConfirm_(pDevDesc dd)
@@ -89,8 +96,8 @@ void RGraphicsDevice::Clip(double x0, double x1, double y0, double y1, pDevDesc
void RGraphicsDevice::Rect(double x0, double y0, double x1, double y1, const pGEcontext gc, pDevDesc dev)
{
qDebug()<<"RGD: Rect";
//XXXqDebughandler::rect(x0, y0, x1, y1, gc, dev);
//XXX todo honour colors
if (rtool && rtool->canvas) rtool->canvas->rectangle(x0,y0,x1,y1,QPen(Qt::white),QBrush(Qt::NoBrush));
}
void RGraphicsDevice::Path(double *x, double *y, int npoly, int *nper, Rboolean winding, const pGEcontext gc, pDevDesc dd)
@@ -114,21 +121,20 @@ SEXP RGraphicsDevice::Cap(pDevDesc dd)
void RGraphicsDevice::Circle(double x, double y, double r, const pGEcontext gc, pDevDesc dev)
{
qDebug()<<"RGD: Circle";
//XXXqDebughandler::circle(x, y, r, gc, dev);
//XXX todo honour colors
if (rtool && rtool->canvas) rtool->canvas->circle(x,y,r,QPen(Qt::white),QBrush(Qt::NoBrush));
}
void RGraphicsDevice::Line(double x1, double y1, double x2, double y2, const pGEcontext gc, pDevDesc dev)
{
qDebug()<<"RGD: Line";
//XXXqDebughandler::line(x1, y1, x2, y2, gc, dev);
//XXX todo honour colors
if (rtool && rtool->canvas) rtool->canvas->line(x1,y1,x2,y2,QPen(Qt::white));
}
void RGraphicsDevice::Polyline(int n, double *x, double *y, const pGEcontext gc, pDevDesc dev)
{
qDebug()<<"RGD: PolyLine"<<n;
for(int i=0; i<n; i++) qDebug()<<i<<":"<<x[i]<<y[i];
//XXXqDebughandler::polyline(n, x, y, gc, dev);
//XXX todo honour colors
if (rtool && rtool->canvas && n > 1) rtool->canvas->polyline(n,x,y,QPen(Qt::white));
}
void RGraphicsDevice::Polygon(int n, double *x, double *y, const pGEcontext gc, pDevDesc dev)
@@ -177,38 +183,6 @@ void RGraphicsDevice::TextUTF8(double x, double y, const char *str, double rot,
//XXXqDebughandler::text(x, y, str, rot, hadj, gc, dev);
}
Rboolean RGraphicsDevice::Locator(double *x, double *y, pDevDesc dev)
{
qDebug()<<"RGD: Locator";
#if 0
if (s_locatorFunction)
{
s_graphicsDeviceEvents.onDrawing();
if(s_locatorFunction(x,y))
{
// if our graphics device went away while we were waiting
// for locator input then we need to return false
if (s_pGEDevDesc != NULL)
return TRUE;
else
return FALSE;
}
else
{
s_graphicsDeviceEvents.onDrawing();
return FALSE;
}
}
else
{
return FALSE;
}
#endif
return FALSE;
}
void RGraphicsDevice::Activate(pDevDesc dev)
{
qDebug()<<"RGD: Activate";
@@ -359,7 +333,7 @@ SEXP RGraphicsDevice::createGD()
pDev->line = RGraphicsDevice::Line;
pDev->polyline = RGraphicsDevice::Polyline;
pDev->polygon = RGraphicsDevice::Polygon;
pDev->locator = RGraphicsDevice::Locator;
pDev->locator = NULL;
pDev->mode = RGraphicsDevice::Mode;
pDev->metricInfo = RGraphicsDevice::MetricInfo;
pDev->strWidth = RGraphicsDevice::StrWidth;
@@ -382,7 +356,7 @@ SEXP RGraphicsDevice::createGD()
pDev->haveTransparentBg = 2;
pDev->haveRaster = 2;
pDev->haveCapture = 1;
pDev->haveLocator = 2;
pDev->haveLocator = 0;
//XXX todo - not sure what we might need
pDev->deviceSpecific = NULL;
@@ -430,10 +404,6 @@ bool RGraphicsDevice::isActive()
return gcGEDevDesc != NULL && Rf_ndevNumber(gcGEDevDesc->dev) == Rf_curDevice();
}
SEXP RGraphicsDevice::GCactivate()
{
return rtool->dev->activateGD();
}
SEXP RGraphicsDevice::activateGD()
{
@@ -443,13 +413,6 @@ SEXP RGraphicsDevice::activateGD()
return R_NilValue;
}
#if 0
DisplaySize RGraphicsDevice::displaySize()
{
return DisplaySize(s_width, s_height);
}
#endif
double RGraphicsDevice::grconvert(double val, const std::string& type, const std::string& from, const std::string& to)
{
//XXXr::exec::RFunction grconvFunc("graphics:::grconvert" + type, val, from, to);
@@ -546,47 +509,6 @@ const int kDefaultWidth = 500;
const int kDefaultHeight = 500;
const double kDefaultDevicePixelRatio = 1.0;
bool RGraphicsDevice::initialize()
{
#if 0
// initialize shadow handler
//XXXX ???? r::session::graphics::handler::installShadowHandler();
// save reference to locator function
//XXX don't want this ???? s_locatorFunction = locatorFunction;
// device conversion functions
UnitConversionFunctions convert;
convert.deviceToUser = deviceToUser;
convert.deviceToNDC = deviceToNDC;
// create plot manager (provide functions & events)
GraphicsDeviceFunctions graphicsDevice;
graphicsDevice.isActive = isActive;
graphicsDevice.displaySize = displaySize;
graphicsDevice.convert = convert;
graphicsDevice.saveSnapshot = saveSnapshot;
graphicsDevice.restoreSnapshot = restoreSnapshot;
graphicsDevice.copyToActiveDevice = copyToActiveDevice;
graphicsDevice.imageFileExtension = imageFileExtension;
graphicsDevice.close = close;
graphicsDevice.onBeforeExecute = onBeforeExecute;
Error error = plotManager().initialize(graphicsPath,
graphicsDevice,
&s_graphicsDeviceEvents);
if (error)
return error;
// set size
setSize(kDefaultWidth, kDefaultHeight, kDefaultDevicePixelRatio);
#endif
// register device creation routine
(*(rtool->R))["GC.display"] = Rcpp::InternalFunction(GCdisplay);
return true;
}
void RGraphicsDevice::setDeviceAttributes(pDevDesc pDev)
{
double pointsize = 12;

View File

@@ -52,7 +52,6 @@ class RGraphicsDevice {
// exported as R methods
static SEXP GCdisplay(); // R> GC.display()
static SEXP GCactivate(); // R> GC.activate()
// R Graphic Device API methods
static void NewPage(const pGEcontext gc, pDevDesc dev);
@@ -76,7 +75,6 @@ class RGraphicsDevice {
static void Activate(pDevDesc dev);
static void Deactivate(pDevDesc dev);
static void Close(pDevDesc dev);
static Rboolean Locator(double *x, double *y, pDevDesc dev);
static void OnExit(pDevDesc dd);
static int HoldFlush(pDevDesc dd, int level);
@@ -95,6 +93,7 @@ class RGraphicsDevice {
SEXP createGD();
SEXP activateGD();
bool initialize();
void setDeviceAttributes(pDevDesc pDev);
bool isActive();
bool makeActive();
@@ -104,9 +103,10 @@ class RGraphicsDevice {
double grconvertY(double y, const std::string& from, const std::string& to);
void deviceToUser(double* x, double* y);
void deviceToNDC(double* x, double* y);
// resizing the display
void setSize(int width, int height, double devicePixelRatio);
void setSize(pDevDesc pDev);
void setDeviceAttributes(pDevDesc pDev);
int getWidth();
int getHeight();
double devicePixelRatio();

View File

@@ -57,6 +57,7 @@ RTool::RTool(int argc, char**argv)
// set the "GC" object and methods
context = NULL;
canvas = NULL;
(*R)["GC.version"] = VERSION_STRING;
(*R)["GC.build"] = VERSION_LATEST;

View File

@@ -33,6 +33,10 @@ class RTool {
RCallbacks *callbacks;
RGraphicsDevice *dev;
// the canvas to plot on, it may be null
// if no canvas is active
RCanvas *canvas;
Context *context;
QString version;

View File

@@ -263,9 +263,9 @@ contains(DEFINES, "GC_WANT_R") {
QMAKE_CXXFLAGS += $$RCPPWARNING $$RCPPFLAGS $$RCPPINCL $$RINSIDEINCL
LIBS += $$RLDFLAGS $$RBLAS $$RLAPACK $$RCPPLIBS $$RINSIDELIBS
## Chart, Tool (R api), Grahics (R device bridge to QWidget)
HEADERS += Charts/RChart.h Charts/RTool.h Charts/RGraphicsDevice.h
SOURCES += Charts/RChart.cpp Charts/RTool.cpp Charts/RGraphicsDevice.cpp
## Chart, Tool (R api), Grahics (R GraphicDevice and Qt canvas widget)
HEADERS += Charts/RChart.h Charts/RTool.h Charts/RGraphicsDevice.h Charts/RCanvas.h
SOURCES += Charts/RChart.cpp Charts/RTool.cpp Charts/RGraphicsDevice.cpp Charts/RCanvas.cpp
}