diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java index 3298e3fbb4629cba0cb66187666b332df7406cc4..a83a14c965abd5bcac251a40c413dd2a97285694 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java @@ -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 diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCairo.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCairo.java index 5001af1fd813ade89275b4a5ba1e9156310d3cf5..e8051d5c63c0263639650ca2a42699774dc20f65 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCairo.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/grDevices/DevCairo.java @@ -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); } diff --git a/documentation/graphics.md b/documentation/graphics.md index e5b13a857ae5abe437e365b04e3f3ef4a2ce4d94..e940d16f39881787e71e3142786bacf1fd341b61 100644 --- a/documentation/graphics.md +++ b/documentation/graphics.md @@ -1,7 +1,7 @@ # 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.