diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java
index c17dbe5d15ea6d8176e885aa117d128cacaf43a7..49d587cf373cb7933e125e97704b7f35403b9570 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GPar.java
@@ -11,6 +11,8 @@
  */
 package com.oracle.truffle.r.library.fastrGrid;
 
+import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asDouble;
+
 import java.util.Arrays;
 
 import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
@@ -97,6 +99,10 @@ public final class GPar {
         return RDataFactory.createList(data, NAMES_VECTOR);
     }
 
+    public static double getCex(RList gpar) {
+        return asDouble(gpar.getDataAt(GP_CEX));
+    }
+
     public static DrawingContext asDrawingContext(RList gpar) {
         return new GParDrawingContext(gpar);
     }
@@ -114,22 +120,40 @@ public final class GPar {
 
         @Override
         public String getColor() {
-            String result = RRuntime.asString(data[GP_COL]);
-            if (!result.startsWith("#")) {
-                result = ColorNames.findByName(result);
-            }
-            return result == null ? "#FFFFFF" : result;
+            return getHexColor(GP_COL);
+        }
+
+        @Override
+        public void setColor(String hexCode) {
+            data[GP_COL] = hexCode;
         }
 
         @Override
         public double getFontSize() {
-            return GridUtils.asDouble(data[GP_FONTSIZE]) * GridUtils.asDouble(data[GP_CEX]);
+            return asDouble(data[GP_FONTSIZE]) * asDouble(data[GP_CEX]);
         }
 
         @Override
         public double getLineHeight() {
-            return GridUtils.asDouble(data[GP_LINEHEIGHT]);
+            return asDouble(data[GP_LINEHEIGHT]);
         }
 
+        @Override
+        public String getFillColor() {
+            return getHexColor(GP_FILL);
+        }
+
+        @Override
+        public void setFillColor(String hexCode) {
+            data[GP_FILL] = hexCode;
+        }
+
+        private String getHexColor(int index) {
+            String result = RRuntime.asString(data[index]);
+            if (!result.startsWith("#")) {
+                result = ColorNames.findByName(result);
+            }
+            return result == null ? "#FFFFFF" : result;
+        }
     }
 }
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java
index 782150456ab9f823912270eed68a1e3a76e30792..0de2442c7b6c55ce38242daec0a3d6be4c9eefdf 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtils.java
@@ -19,8 +19,7 @@ import com.oracle.truffle.r.library.fastrGrid.Unit.UnitLengthNode;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.data.RAttributable;
-import com.oracle.truffle.r.runtime.data.RDouble;
-import com.oracle.truffle.r.runtime.data.RInteger;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractContainer;
@@ -116,7 +115,7 @@ final class GridUtils {
 
     static RAbstractIntVector asIntVector(Object value) {
         if (value instanceof Integer) {
-            return RInteger.valueOf((Integer) value);
+            return RDataFactory.createIntVectorFromScalar((Integer) value);
         } else if (value instanceof RAbstractIntVector) {
             return (RAbstractIntVector) value;
         }
@@ -125,7 +124,7 @@ final class GridUtils {
 
     public static RAbstractDoubleVector asDoubleVector(Object obj) {
         if (obj instanceof Double) {
-            return RDouble.valueOf((Double) obj);
+            return RDataFactory.createDoubleVectorFromScalar((Double) obj);
         } else if (obj instanceof RAbstractDoubleVector) {
             return (RAbstractDoubleVector) obj;
         }
@@ -134,9 +133,9 @@ final class GridUtils {
 
     static RAbstractContainer asAbstractContainer(Object value) {
         if (value instanceof Integer) {
-            return RInteger.valueOf((Integer) value);
+            return RDataFactory.createIntVectorFromScalar((Integer) value);
         } else if (value instanceof Double) {
-            return RDouble.valueOf((Double) value);
+            return RDataFactory.createDoubleVectorFromScalar((Double) value);
         } else if (value instanceof RAbstractContainer) {
             return (RAbstractContainer) value;
         }
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
new file mode 100644
index 0000000000000000000000000000000000000000..5c40420695731eb0b9f2f03193bfab771dbac857
--- /dev/null
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java
@@ -0,0 +1,141 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (C) 2001-3 Paul Murrell
+ * Copyright (c) 1998-2015, The R Core Team
+ * Copyright (c) 2017, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.library.fastrGrid;
+
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.numericValue;
+
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext;
+import com.oracle.truffle.r.library.fastrGrid.Unit.UnitLengthNode;
+import com.oracle.truffle.r.library.fastrGrid.Unit.UnitToInchesNode;
+import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode;
+import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode;
+import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
+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) */
+    private static final double DMDC = 1.25331413731550025119; /* sqrt(pi / 4) * sqrt(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 */
+    private static final String TRANSPARENT = "white";  // TODO: should be actually transparent
+
+    @Child private GetViewPortTransformNode getViewPortTransform = new GetViewPortTransformNode();
+    @Child private VPContextFromVPNode vpContextFromVP = new VPContextFromVPNode();
+    @Child private UnitLengthNode unitLength = Unit.createLengthNode();
+    @Child private UnitToInchesNode unitToInches = Unit.createToInchesNode();
+
+    static {
+        Casts casts = new Casts(LPoints.class);
+        casts.arg(0).mustBe(abstractVectorValue());
+        casts.arg(1).mustBe(abstractVectorValue());
+        casts.arg(2).mustBe(numericValue(), Message.GENERIC, "grid.points: pch argument not implemented for characters yet").asIntegerVector();
+        casts.arg(3).mustBe(abstractVectorValue());
+    }
+
+    public static LPoints create() {
+        return LPointsNodeGen.create();
+    }
+
+    @Specialization
+    public Object doPoints(RAbstractVector xVec, RAbstractVector yVec, RAbstractIntVector pchVec, RAbstractVector sizeVec) {
+        GridContext ctx = GridContext.getContext();
+        GridDevice dev = ctx.getCurrentDevice();
+
+        RList currentVP = ctx.getGridState().getViewPort();
+        RList gpar = ctx.getGridState().getGpar();
+        DrawingContext drawingCtx = GPar.asDrawingContext(gpar);
+        double cex = GPar.getCex(gpar);
+        ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
+        ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
+        UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
+
+        // Note: unlike in other drawing primitives, we only consider length of x
+        int length = unitLength.execute(xVec);
+        for (int i = 0; i < length; i++) {
+            Point loc = TransformMatrix.transLocation(Point.fromUnits(unitToInches, xVec, yVec, i, conversionCtx), vpTransform.transform);
+            double size = unitToInches.convertWidth(sizeVec, i, conversionCtx);
+            if (loc.isFinite() && Double.isFinite(size)) {
+                drawSymbol(drawingCtx, dev, cex, pchVec.getDataAt(i % pchVec.getLength()), size, loc.x, loc.y);
+            }
+        }
+        return RNull.instance;
+    }
+
+    // transcribed from engine.c function GESymbol
+
+    private void drawSymbol(DrawingContext 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
+        // interpreted as corresponding ascii character, which should be drawn
+        switch (pch) {
+            case 46:
+                drawDot(drawingCtx, dev, cex, x, y);
+                break;
+            case 1:
+                drawOctahedron(drawingCtx, dev, GridColor.TRANSPARENT, size, x, y);
+                break;
+            case 16:
+                drawOctahedron(drawingCtx, dev, drawingCtx.getColor(), size, x, y);
+                break;
+            default:
+                throw RInternalError.unimplemented("grid.points unimplemented symbol " + pch);
+        }
+    }
+
+    private static void drawOctahedron(DrawingContext drawingCtx, GridDevice dev, GridColor fill, double size, double x, double y) {
+        GridColor originalFill = drawingCtx.getFillColor();
+        drawingCtx.setFillColor(fill);
+        dev.drawCircle(drawingCtx, x, y, RADIUS * size);
+        drawingCtx.setFillColor(originalFill);
+    }
+
+    private static void drawDot(DrawingContext drawingCtx, 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)
+        String originalFill = drawingCtx.getFillColor();
+        drawingCtx.setFillColor(drawingCtx.getColor());
+        drawingCtx.setColor(TRANSPARENT);
+
+        /*
+         * 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.
+         */
+        double xc = cex * 0.005;
+        double yc = cex * 0.005;
+        if (cex > 0 && xc < 0.5) {
+            xc = 0.5;
+        }
+        if (cex > 0 && yc < 0.5) {
+            yc = 0.5;
+        }
+        dev.drawRect(drawingCtx, x - xc, y - yc, x + xc, y + yc);
+
+        drawingCtx.setColor(drawingCtx.getFillColor());
+        drawingCtx.setFillColor(originalFill);
+    }
+}
diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java
index facac095e99e7217f318bd2110c645f0d965b2b9..1cd94bdabf4384ebf90917e9ce5f86d12af7b3fd 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/DrawingContext.java
@@ -34,6 +34,12 @@ public interface DrawingContext {
      */
     String getColor();
 
+    /**
+     * Alows to set the color. The parameter may also be a synonym defined in
+     * {@link com.oracle.truffle.r.library.fastrGrid.ColorNames}.
+     */
+    void setColor(String hexCode);
+
     /**
      * Gets the font size in points.
      *
@@ -45,4 +51,16 @@ public interface DrawingContext {
      * Gets the height of a line in multiplies of the base line height.
      */
     double getLineHeight();
+
+    /**
+     * @return Hexadecimal string of the color with leading '#', e.g. '#FFA8B2'. Never returns a
+     *         synonym.
+     */
+    String getFillColor();
+
+    /**
+     * Alows to set the fill color. The parameter may also be a synonym defined in
+     * {@link com.oracle.truffle.r.library.fastrGrid.ColorNames}.
+     */
+    void setFillColor(String hexCode);
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
index 9a10b92b977ab7968dc34fc0765fa0d3e12f47c0..6b0b5d88beed9b7238b8802637835e00132d6dcf 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/CallAndExternalFunctions.java
@@ -34,6 +34,7 @@ import com.oracle.truffle.r.library.fastrGrid.LInitGrid;
 import com.oracle.truffle.r.library.fastrGrid.LInitViewPortStack;
 import com.oracle.truffle.r.library.fastrGrid.LLines;
 import com.oracle.truffle.r.library.fastrGrid.LNewPage;
+import com.oracle.truffle.r.library.fastrGrid.LPoints;
 import com.oracle.truffle.r.library.fastrGrid.LRect;
 import com.oracle.truffle.r.library.fastrGrid.LSegments;
 import com.oracle.truffle.r.library.fastrGrid.LText;
@@ -709,6 +710,8 @@ public class CallAndExternalFunctions {
                     return LSegments.create();
                 case "L_circle":
                     return LCircle.create();
+                case "L_points":
+                    return LPoints.create();
 
                 // Simple grid state access
                 case "L_getGPar":
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index 8119e383c89cffe938e9d9ea2fec61c315c8f675..2ed282f563cbf7bbb51df5007804cadf27f655a7 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -767,6 +767,7 @@ com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/p
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/deriv/Deriv.java,gnu_r_gentleman_ihaka2.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/stats/deriv/DerivVisitor.java,gnu_r_gentleman_ihaka2.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LRect.java,gnu_r_murrel_core.copyright
+com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LPoints.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/DoSetViewPortBuiltin.java,gnu_r_murrel_core.copyright
 com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LUpViewPort.java,gnu_r_murrel_core.copyright