Skip to content
Snippets Groups Projects
Commit e342f09b authored by Stepan Sindelar's avatar Stepan Sindelar
Browse files

[GR-2798] Small grid fixes.

parents d103f715 e6b9f282
No related branches found
No related tags found
No related merge requests found
......@@ -22,40 +22,24 @@ import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
import com.oracle.truffle.r.runtime.RError.Message;
import com.oracle.truffle.r.runtime.RInternalError;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
private static final double SMALL = 0.25;
private static final double RADIUS = 0.375;
private static final double SQRC = 0.88622692545275801364; /* sqrt(pi / 4) */
@SuppressWarnings("unused") private static final double DMDC = 1.25331413731550025119; /*
* sqrt(
* pi /
* 4) *
* sqrt(
* 2)
*/
@SuppressWarnings("unused") private static final double TRC0 = 1.55512030155621416073; /*
* sqrt(4
* *
* pi/(3
* *
* sqrt(3
* )))
*/
@SuppressWarnings("unused") private static final double TRC1 = 1.34677368708859836060; /*
* TRC0 *
* sqrt(
* 3) / 2
*/
@SuppressWarnings("unused") private static final double TRC2 = 0.77756015077810708036; /*
* TRC0 /
* 2
*/
private static final double TRC0 = 1.55512030155621416073; /* sqrt(4 * pi/(3 * sqrt(3))) */
private static final double TRC1 = 1.34677368708859836060; /* TRC0 * sqrt(3) / 2 */
private static final double TRC2 = 0.77756015077810708036; /* TRC0 / 2 */
// empiracally chosen to match GNU R look better
private static final double TRIANGLE_SIZE_FACTOR = 1.15;
// empirically chosen factor to visually approx match GNU R
private static final double SIZE_FACTOR = 0.375;
// we assume at leat 72 points per inch
private static final double PIXEL_SIZE = 1. / 72.;
static {
Casts casts = new Casts(LPoints.class);
......@@ -85,120 +69,225 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
// Note: unlike in other drawing primitives, we only consider length of x
int length = Unit.getLength(xVec);
DrawingContext initialDrawingCtx = gpar.getDrawingContext(0);
PointDrawingContext pointDrawingCtx = new PointDrawingContext(initialDrawingCtx, initialDrawingCtx.getFillColor(), initialDrawingCtx.getFillColor());
ContextCache contextCache = new ContextCache(null);
for (int i = 0; i < length; i++) {
Point loc = TransformMatrix.transLocation(Point.fromUnits(xVec, yVec, i, conversionCtx), vpTransform.transform);
double size = Unit.convertWidth(sizeVec, i, conversionCtx);
if (loc.isFinite() && Double.isFinite(size)) {
pointDrawingCtx = pointDrawingCtx.update(gpar.getDrawingContext(i));
pointDrawingCtx = drawSymbol(pointDrawingCtx, dev, cex, pchVec.getDataAt(i % pchVec.getLength()), size, loc.x, loc.y);
contextCache = contextCache.from(gpar.getDrawingContext(i));
drawSymbol(contextCache, dev, cex, pchVec.getDataAt(i % pchVec.getLength()), size * SIZE_FACTOR, loc.x, loc.y);
}
}
return RNull.instance;
}
// transcribed from engine.c function GESymbol
private static PointDrawingContext drawSymbol(PointDrawingContext drawingCtx, GridDevice dev, double cex, int pch, double size, double x, double y) {
private static void drawSymbol(ContextCache ctxCache, GridDevice dev, double cex, int pch, double halfSize, double x, double y) {
// pch 0 - 25 are interpreted as geometrical shapes, pch from ascii code of ' ' are
// interpreted as corresponding ascii character, which should be drawn
// the coordinates should be interpreted as the center of the symbol
double fullSize = halfSize * 2;
DrawingContext emptyFill = ctxCache.getTransparentFill();
switch (pch) {
case 46:
return drawDot(drawingCtx, dev, cex, x, y);
case 0:
drawSquare(emptyFill, dev, halfSize, x, y);
break;
case 1:
return drawOctahedron(drawingCtx, dev, GridColor.TRANSPARENT, size, x, y);
case 19: /* R filled circle */
return drawFilledCircle(drawingCtx, dev, RADIUS * size, x, y);
case 20: /* R `Dot' (small circle) */
return drawFilledCircle(drawingCtx, dev, SMALL * size, x, y);
case 21: /* circles */
return drawCircle(drawingCtx, dev, size, x, y);
case 22: /* squares */
return drawSquare(drawingCtx, dev, size, x, y);
case 16:
return drawOctahedron(drawingCtx, dev, drawingCtx.getWrapped().getColor(), size, x, y);
dev.drawCircle(emptyFill, x, y, halfSize);
break;
case 2: // triangle up
triangleUp(emptyFill, dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
break;
case 3: /* S plus */
drawPlus(emptyFill, dev, halfSize, x, y);
break;
case 4: // S times
drawTimes(emptyFill, dev, halfSize, x, y);
break;
case 5: // S diamond
drawDiamond(emptyFill, dev, halfSize, fullSize, x, y);
break;
case 6: // S triangle point down
triangleDown(emptyFill, dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
break;
case 7: // S square and times superimposed
drawSquare(emptyFill, dev, halfSize, x, y);
drawTimes(emptyFill, dev, halfSize, x, y);
break;
case 8: // S times and plus superimposed
drawPlus(emptyFill, dev, halfSize, x, y);
drawTimes(emptyFill, dev, halfSize, x, y);
break;
case 9: // S diamond and plus superimposed
drawPlus(emptyFill, dev, halfSize, x, y);
drawDiamond(emptyFill, dev, halfSize, fullSize, x, y);
break;
case 10: // S circle and plus
dev.drawCircle(emptyFill, x, y, halfSize);
drawPlus(emptyFill, dev, halfSize, x, y);
break;
case 11: // S superimposed triangles
triangleUp(emptyFill, dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
triangleDown(emptyFill, dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
break;
case 12: // S square and plus superimposed
drawSquare(emptyFill, dev, halfSize, x, y);
drawPlus(emptyFill, dev, halfSize, x, y);
break;
case 13: // S circle and times
dev.drawCircle(emptyFill, x, y, halfSize);
drawTimes(ctxCache.original, dev, halfSize, x, y);
break;
case 14: // S rectangle with triangle up
dev.drawRect(emptyFill, x - halfSize, y - halfSize, fullSize, fullSize, 0);
drawConnected(ctxCache.getTransparentFill(), dev, x - halfSize, y - halfSize, x + halfSize, y - halfSize, x, y + halfSize);
break;
case 15: // S filled square
case 22: // S filled (with different color) square
dev.drawRect(ctxCache.getFilled(), x - halfSize, y - halfSize, fullSize, fullSize, 0);
break;
case 16: // S filled circle (should be 'octagon')
case 19: // S filled circle
case 21: // S filled (with different color) circle
dev.drawCircle(ctxCache.getFilled(), x, y, halfSize);
break;
case 17: // S filled triangle up
case 24: // S filled (with different color) triangle up
triangleUp(ctxCache.getFilled(), dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
break;
case 18: // S filled diamond
case 23: // S filled (with different color) diamond
drawDiamond(ctxCache.getFilled(), dev, halfSize, fullSize, x, y);
break;
case 20: // S smaller filled circle
dev.drawCircle(ctxCache.getFilled(), x, y, halfSize * .6);
break;
case 25: // S triangle down filled
triangleDown(ctxCache.getFilled(), dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
break;
case 46: // small dot
// we assume at leat 72 points per inch
dev.drawRect(ctxCache.getFilled(), x - PIXEL_SIZE / 2, y - PIXEL_SIZE / 2, PIXEL_SIZE, PIXEL_SIZE, 0);
break;
default:
throw RInternalError.unimplemented("grid.points unimplemented symbol " + pch);
drawTextSymbol(ctxCache, dev, x, y, new String(new char[]{(char) pch}));
}
}
private static PointDrawingContext drawFilledCircle(PointDrawingContext drawingCtxIn, GridDevice dev, double radius, double x, double y) {
PointDrawingContext drawingCtx = drawingCtxIn.update(drawingCtxIn.getWrapped().getColor(), drawingCtxIn.getWrapped().getColor());
dev.drawCircle(drawingCtx, x, y, radius);
return drawingCtx;
private static void drawDiamond(DrawingContext ctx, GridDevice dev, double halfSize, double fullSize, double x, double y) {
dev.drawRect(ctx, x - halfSize, y - halfSize, fullSize, fullSize, 1.75 * Math.PI);
}
private static void drawSquare(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
double fullSize = halfSize * 2.;
dev.drawRect(ctx, x - halfSize, y - halfSize, fullSize, fullSize, 0);
}
private static void drawTimes(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
drawLine(ctx, dev, x - halfSize, y + halfSize, x + halfSize, y - halfSize);
drawLine(ctx, dev, x + halfSize, y + halfSize, x - halfSize, y - halfSize);
}
private static void drawPlus(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
drawLine(ctx, dev, x - halfSize, y, x + halfSize, y);
drawLine(ctx, dev, x, y + halfSize, x, y - halfSize);
}
private static PointDrawingContext drawCircle(PointDrawingContext drawingCtx, GridDevice dev, double size, double x, double y) {
double xc = RADIUS * size;
dev.drawCircle(drawingCtx, x, y, xc);
return drawingCtx;
private static void triangleDown(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
double yc = halfSize * TRC2;
double xc = halfSize * TRC1;
drawConnected(ctx, dev, x, y - halfSize * TRC0, x - xc, y + yc, x + xc, y + yc);
}
private static PointDrawingContext drawSquare(PointDrawingContext drawingCtx, GridDevice dev, double size, double x, double y) {
double xc = RADIUS * SQRC * size;
double yc = RADIUS * SQRC * size;
dev.drawRect(drawingCtx, x - xc, y - yc, x + xc, y + yc, 0);
return drawingCtx;
private static void triangleUp(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
double yc = halfSize * TRC2;
double xc = halfSize * TRC1;
drawConnected(ctx, dev, x, y + halfSize * TRC0, x - xc, y - yc, x + xc, y - yc);
}
private static PointDrawingContext drawOctahedron(PointDrawingContext drawingCtxIn, GridDevice dev, GridColor fill, double size, double x, double y) {
PointDrawingContext drawingCtx = drawingCtxIn.update(drawingCtxIn.getWrapped().getColor(), fill);
dev.drawCircle(drawingCtx, x, y, RADIUS * size);
return drawingCtx;
private static void drawTextSymbol(ContextCache ctxCache, GridDevice dev, double x, double y, String symbols) {
double height = dev.getStringHeight(ctxCache.getSymbol(), symbols);
double width = dev.getStringWidth(ctxCache.getSymbol(), symbols);
dev.drawString(ctxCache.getSymbol(), x - width / 2, y - height / 2, 0, symbols);
}
private static PointDrawingContext drawDot(PointDrawingContext drawingCtxIn, GridDevice dev, double cex, double x, double y) {
// NOTE: we are *filling* a rect with the current colour (we are not drawing the border AND
// we are not using the current fill colour)
PointDrawingContext drawingCtx = drawingCtxIn.update(GridColor.TRANSPARENT, drawingCtxIn.getWrapped().getColor());
/*
* The idea here is to use a 0.01" square, but to be of at least one device unit in each
* direction, assuming that corresponds to pixels. That may be odd if pixels are not square,
* but only on low resolution devices where we can do nothing better.
*
* For this symbol only, size is cex (see engine.c).
*
* Prior to 2.1.0 the offsets were always 0.5.
/**
* Simpler to use by hand version of drawPolyline. Points are expected to be in format [x1, y1,
* x2, y2, ...].
*/
private static void drawConnected(DrawingContext ctx, GridDevice dev, double... points) {
assert points.length % 2 == 0 && points.length > 0;
double[] x = new double[(points.length / 2) + 1];
double[] y = new double[(points.length / 2) + 1];
x[x.length - 1] = points[0];
y[y.length - 1] = points[1];
for (int i = 0; i < x.length - 1; i++) {
x[i] = points[i * 2];
y[i] = points[(i * 2) + 1];
}
dev.drawPolygon(ctx, x, y, 0, y.length);
}
private static void drawLine(DrawingContext ctx, GridDevice dev, double x1, double y1, double x2, double y2) {
dev.drawPolyLines(ctx, new double[]{x1, x2}, new double[]{y1, y2}, 0, 2);
}
private static final class ContextCache {
public final DrawingContext original;
private DrawingContext filled;
private DrawingContext transprentFill;
private DrawingContext symbol;
private ContextCache(DrawingContext original) {
this.original = original;
}
ContextCache from(DrawingContext newOriginal) {
if (original == newOriginal) {
return this;
}
return new ContextCache(newOriginal);
}
/**
* Context with fill color set to the normal color of the original context.
*/
double xc = cex * 0.005;
double yc = cex * 0.005;
if (cex > 0 && xc < 0.5) {
xc = 0.5;
DrawingContext getFilled() {
if (filled == null) {
filled = new PointDrawingContext(original, original.getColor(), original.getColor(), 1);
}
return filled;
}
if (cex > 0 && yc < 0.5) {
yc = 0.5;
DrawingContext getTransparentFill() {
if (transprentFill == null) {
transprentFill = new PointDrawingContext(original, original.getColor(), GridColor.TRANSPARENT, 1);
}
return transprentFill;
}
DrawingContext getSymbol() {
if (symbol == null) {
symbol = new PointDrawingContext(original, original.getColor(), original.getFillColor(), 1.4);
}
return symbol;
}
dev.drawRect(drawingCtx, x - xc, y - yc, x + xc, y + yc, 0);
return drawingCtx;
}
/**
* Context that has the same parameters as the given context except for the color and fill color
* and multiplication factor for font size, which are given explicitly.
*/
private static final class PointDrawingContext implements DrawingContext {
private final DrawingContext inner;
private final GridColor color;
private final GridColor fillColor;
private final double fontsizeFactor;
private PointDrawingContext(DrawingContext inner, GridColor color, GridColor fillColor) {
private PointDrawingContext(DrawingContext inner, GridColor color, GridColor fillColor, double fontsizeFactor) {
this.inner = inner;
this.color = color;
this.fillColor = fillColor;
}
// This allows to re-use the existing instance if it would have the same parameters. The
// assumption is that the users will actually draw many points in a row with the same
// parameters.
private PointDrawingContext update(GridColor newColor, GridColor newFillColor) {
if (this.color.equals(newColor) && this.fillColor.equals(newFillColor)) {
return this;
}
return new PointDrawingContext(inner, newColor, newFillColor);
}
private PointDrawingContext update(DrawingContext newInner) {
if (this.inner == newInner) {
return this;
}
return new PointDrawingContext(newInner, this.color, this.fillColor);
this.fontsizeFactor = fontsizeFactor;
}
@Override
......@@ -233,7 +322,7 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
@Override
public double getFontSize() {
return inner.getFontSize();
return inner.getFontSize() * fontsizeFactor;
}
@Override
......
......@@ -43,7 +43,7 @@ public class DevCairo extends RExternalBuiltinNode {
String filename = RRuntime.asString(args.getArgument(0));
int witdh = RRuntime.asInteger(args.getArgument(2));
int height = RRuntime.asInteger(args.getArgument(2));
int height = RRuntime.asInteger(args.getArgument(3));
if (RRuntime.isNA(witdh) || RRuntime.isNA(height) || RRuntime.isNA(filename) || filename.isEmpty()) {
throw error(Message.INVALID_ARG_TYPE);
}
......
# Introduction
There are two main built-in R packages that provide basic graphical output
support: *graphics* and *lattice*. They both use some parts of even lower level
support: *graphics* and *grid*. They both use some parts of even lower level
*grDevices* package, which allows users to change the "device" to which the
graphical output will be drawn. The probably most popular graphical packages
*lattice* and *ggplot2* are build on top of the *grid* package.
......@@ -84,5 +84,13 @@ method and pass the graphics object to R code using `PolyglotEngine`.
The R code can do any *grid* based visualization and it will be directly
displayed in the UI.
# Limitations
FastR's grid implementation does not yet support:
* expressions in `grid.text`
* `grid.xspline` function
* clipping
FastR does not plan to implement the R graphics engine display list
and related functions. However, the grid display list is implemented.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment