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
Branches
No related tags found
No related merge requests found
...@@ -22,40 +22,24 @@ import com.oracle.truffle.r.library.fastrGrid.device.GridColor; ...@@ -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.library.fastrGrid.device.GridDevice;
import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode; import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
import com.oracle.truffle.r.runtime.RError.Message; 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.RList;
import com.oracle.truffle.r.runtime.data.RNull; 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.RAbstractIntVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector; import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
public abstract class LPoints extends RExternalBuiltinNode.Arg4 { public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
private static final double SMALL = 0.25; private static final double TRC0 = 1.55512030155621416073; /* sqrt(4 * pi/(3 * sqrt(3))) */
private static final double RADIUS = 0.375; private static final double TRC1 = 1.34677368708859836060; /* TRC0 * sqrt(3) / 2 */
private static final double SQRC = 0.88622692545275801364; /* sqrt(pi / 4) */ private static final double TRC2 = 0.77756015077810708036; /* TRC0 / 2 */
@SuppressWarnings("unused") private static final double DMDC = 1.25331413731550025119; /*
* sqrt( // empiracally chosen to match GNU R look better
* pi / private static final double TRIANGLE_SIZE_FACTOR = 1.15;
* 4) *
* sqrt( // empirically chosen factor to visually approx match GNU R
* 2) private static final double SIZE_FACTOR = 0.375;
*/
@SuppressWarnings("unused") private static final double TRC0 = 1.55512030155621416073; /* // we assume at leat 72 points per inch
* sqrt(4 private static final double PIXEL_SIZE = 1. / 72.;
* *
* 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
*/
static { static {
Casts casts = new Casts(LPoints.class); Casts casts = new Casts(LPoints.class);
...@@ -85,120 +69,225 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 { ...@@ -85,120 +69,225 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
// Note: unlike in other drawing primitives, we only consider length of x // Note: unlike in other drawing primitives, we only consider length of x
int length = Unit.getLength(xVec); int length = Unit.getLength(xVec);
DrawingContext initialDrawingCtx = gpar.getDrawingContext(0); ContextCache contextCache = new ContextCache(null);
PointDrawingContext pointDrawingCtx = new PointDrawingContext(initialDrawingCtx, initialDrawingCtx.getFillColor(), initialDrawingCtx.getFillColor());
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
Point loc = TransformMatrix.transLocation(Point.fromUnits(xVec, yVec, i, conversionCtx), vpTransform.transform); Point loc = TransformMatrix.transLocation(Point.fromUnits(xVec, yVec, i, conversionCtx), vpTransform.transform);
double size = Unit.convertWidth(sizeVec, i, conversionCtx); double size = Unit.convertWidth(sizeVec, i, conversionCtx);
if (loc.isFinite() && Double.isFinite(size)) { if (loc.isFinite() && Double.isFinite(size)) {
pointDrawingCtx = pointDrawingCtx.update(gpar.getDrawingContext(i)); contextCache = contextCache.from(gpar.getDrawingContext(i));
pointDrawingCtx = drawSymbol(pointDrawingCtx, dev, cex, pchVec.getDataAt(i % pchVec.getLength()), size, loc.x, loc.y); drawSymbol(contextCache, dev, cex, pchVec.getDataAt(i % pchVec.getLength()), size * SIZE_FACTOR, loc.x, loc.y);
} }
} }
return RNull.instance; return RNull.instance;
} }
// transcribed from engine.c function GESymbol private static void drawSymbol(ContextCache ctxCache, GridDevice dev, double cex, int pch, double halfSize, double x, double y) {
private static PointDrawingContext drawSymbol(PointDrawingContext drawingCtx, GridDevice dev, double cex, int pch, double size, double x, double y) {
// pch 0 - 25 are interpreted as geometrical shapes, pch from ascii code of ' ' are // pch 0 - 25 are interpreted as geometrical shapes, pch from ascii code of ' ' are
// interpreted as corresponding ascii character, which should be drawn // 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) { switch (pch) {
case 46: case 0:
return drawDot(drawingCtx, dev, cex, x, y); drawSquare(emptyFill, dev, halfSize, x, y);
break;
case 1: case 1:
return drawOctahedron(drawingCtx, dev, GridColor.TRANSPARENT, size, x, y); dev.drawCircle(emptyFill, x, y, halfSize);
case 19: /* R filled circle */ break;
return drawFilledCircle(drawingCtx, dev, RADIUS * size, x, y); case 2: // triangle up
case 20: /* R `Dot' (small circle) */ triangleUp(emptyFill, dev, halfSize * TRIANGLE_SIZE_FACTOR, x, y);
return drawFilledCircle(drawingCtx, dev, SMALL * size, x, y); break;
case 21: /* circles */ case 3: /* S plus */
return drawCircle(drawingCtx, dev, size, x, y); drawPlus(emptyFill, dev, halfSize, x, y);
case 22: /* squares */ break;
return drawSquare(drawingCtx, dev, size, x, y); case 4: // S times
case 16: drawTimes(emptyFill, dev, halfSize, x, y);
return drawOctahedron(drawingCtx, dev, drawingCtx.getWrapped().getColor(), size, 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: 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) { private static void drawDiamond(DrawingContext ctx, GridDevice dev, double halfSize, double fullSize, double x, double y) {
PointDrawingContext drawingCtx = drawingCtxIn.update(drawingCtxIn.getWrapped().getColor(), drawingCtxIn.getWrapped().getColor()); dev.drawRect(ctx, x - halfSize, y - halfSize, fullSize, fullSize, 1.75 * Math.PI);
dev.drawCircle(drawingCtx, x, y, radius); }
return drawingCtx;
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) { private static void triangleDown(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
double xc = RADIUS * size; double yc = halfSize * TRC2;
dev.drawCircle(drawingCtx, x, y, xc); double xc = halfSize * TRC1;
return drawingCtx; 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) { private static void triangleUp(DrawingContext ctx, GridDevice dev, double halfSize, double x, double y) {
double xc = RADIUS * SQRC * size; double yc = halfSize * TRC2;
double yc = RADIUS * SQRC * size; double xc = halfSize * TRC1;
dev.drawRect(drawingCtx, x - xc, y - yc, x + xc, y + yc, 0); drawConnected(ctx, dev, x, y + halfSize * TRC0, x - xc, y - yc, x + xc, y - yc);
return drawingCtx;
} }
private static PointDrawingContext drawOctahedron(PointDrawingContext drawingCtxIn, GridDevice dev, GridColor fill, double size, double x, double y) { private static void drawTextSymbol(ContextCache ctxCache, GridDevice dev, double x, double y, String symbols) {
PointDrawingContext drawingCtx = drawingCtxIn.update(drawingCtxIn.getWrapped().getColor(), fill); double height = dev.getStringHeight(ctxCache.getSymbol(), symbols);
dev.drawCircle(drawingCtx, x, y, RADIUS * size); double width = dev.getStringWidth(ctxCache.getSymbol(), symbols);
return drawingCtx; 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 * Simpler to use by hand version of drawPolyline. Points are expected to be in format [x1, y1,
// we are not using the current fill colour) * x2, y2, ...].
PointDrawingContext drawingCtx = drawingCtxIn.update(GridColor.TRANSPARENT, drawingCtxIn.getWrapped().getColor()); */
/* private static void drawConnected(DrawingContext ctx, GridDevice dev, double... points) {
* The idea here is to use a 0.01" square, but to be of at least one device unit in each assert points.length % 2 == 0 && points.length > 0;
* direction, assuming that corresponds to pixels. That may be odd if pixels are not square, double[] x = new double[(points.length / 2) + 1];
* but only on low resolution devices where we can do nothing better. double[] y = new double[(points.length / 2) + 1];
* x[x.length - 1] = points[0];
* For this symbol only, size is cex (see engine.c). y[y.length - 1] = points[1];
* for (int i = 0; i < x.length - 1; i++) {
* Prior to 2.1.0 the offsets were always 0.5. 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; DrawingContext getFilled() {
double yc = cex * 0.005; if (filled == null) {
if (cex > 0 && xc < 0.5) { filled = new PointDrawingContext(original, original.getColor(), original.getColor(), 1);
xc = 0.5; }
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 static final class PointDrawingContext implements DrawingContext {
private final DrawingContext inner; private final DrawingContext inner;
private final GridColor color; private final GridColor color;
private final GridColor fillColor; 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.inner = inner;
this.color = color; this.color = color;
this.fillColor = fillColor; this.fillColor = fillColor;
} this.fontsizeFactor = fontsizeFactor;
// 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);
} }
@Override @Override
...@@ -233,7 +322,7 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 { ...@@ -233,7 +322,7 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
@Override @Override
public double getFontSize() { public double getFontSize() {
return inner.getFontSize(); return inner.getFontSize() * fontsizeFactor;
} }
@Override @Override
......
...@@ -43,7 +43,7 @@ public class DevCairo extends RExternalBuiltinNode { ...@@ -43,7 +43,7 @@ public class DevCairo extends RExternalBuiltinNode {
String filename = RRuntime.asString(args.getArgument(0)); String filename = RRuntime.asString(args.getArgument(0));
int witdh = RRuntime.asInteger(args.getArgument(2)); 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()) { if (RRuntime.isNA(witdh) || RRuntime.isNA(height) || RRuntime.isNA(filename) || filename.isEmpty()) {
throw error(Message.INVALID_ARG_TYPE); throw error(Message.INVALID_ARG_TYPE);
} }
......
# Introduction # Introduction
There are two main built-in R packages that provide basic graphical output 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 *grDevices* package, which allows users to change the "device" to which the
graphical output will be drawn. The probably most popular graphical packages graphical output will be drawn. The probably most popular graphical packages
*lattice* and *ggplot2* are build on top of the *grid* package. *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`. ...@@ -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 The R code can do any *grid* based visualization and it will be directly
displayed in the UI. 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.
Please register or to comment