# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1588612736 -7200
#      Mon May 04 19:18:56 2020 +0200
# Node ID 4835ea1a8c513ea11649235dab557b29351d4e13
# Parent  6b6cb174d6bbee96a36a267ef530bbf9c894df66
Add graphics object "scatter" (bug #58282).

* graphics.cc, graphics.in.h (scatter, F__go_scatter__): Implement new
graphics object.
* gl-render.cc, gl-render.h (draw_scatter): Add new function to draw new
graphics objects with OpenGL.
(change_marker): Add new function to change size of marker.
* scripts/plot/draw/private/__scatter__.m: Use new graphics object if
the current graphics toolkit is not "gnuplot". Move the fall back code
for gnuplot to a new function.
* scripts/plot/draw/private/__gnuplot_scatter__.m: New function with the
code moved from __scatter__.m.
* doc/interpreter/genpropdoc.m: Add documentation for scatter
properties.
* doc/interpreter/graphics_properties.mk, module.mk: Add build rules for
documentation of scatter properties.
* doc/interpreter/plot.txi: Add reference to scatter properties. Remove
outdated section for "Scatter Group".
* doc/interpreter/octave.texi: Add new section and delete removed
section from table of contents.
* scatter.m, scatter3.m: Add cross reference to scatter properties in
docstring.

diff -r 6b6cb174d6bb -r 4835ea1a8c51 doc/interpreter/genpropdoc.m
--- a/doc/interpreter/genpropdoc.m	Tue May 05 01:10:39 2020 -0400
+++ b/doc/interpreter/genpropdoc.m	Mon May 04 19:18:56 2020 +0200
@@ -39,9 +39,10 @@
 
 function genpropdoc (objname, fname)
   objnames = {"root", "figure", "axes", ...
-              "image", "light", "line", "patch", "surface", "text", ...
-              "uibuttongroup", "uicontextmenu", "uicontrol", "uipanel", ...
-              "uimenu", "uipushtool", "uitable", "uitoggletool" "uitoolbar"
+              "image", "light", "line", "patch", "scatter", "surface", ...
+              "text", "uibuttongroup", "uicontextmenu", "uicontrol", ...
+              "uipanel", "uimenu", "uipushtool", "uitable", ...
+              "uitoggletool", "uitoolbar"
              };
 
   ## Base properties
@@ -1529,6 +1530,96 @@
 
     endswitch
 
+  ## Scatter properties
+  elseif (strcmp (objname, "scatter"))
+    switch (field)
+      ## Overridden shared properties
+      case "children"
+        s.doc = doc_unused;
+
+      ## Specific properties
+      case "cdatamode"
+      case "cdatasource"
+      case "cdata"
+        s.doc = "Data defining the scatter object color.\n\
+\n\
+If @code{cdata} is a scalar index into the current colormap or a RGB triplet, \
+it defines the color of all scatter markers.\n\
+\n\
+If @code{cdata} is an N-by-1 vector of indices or an N-by-3 (RGB) matrix, \
+it defines the color of each one of the N scatter markers.";
+        s.valid = valid_scalmat;
+
+
+      case "displayname"
+        s.doc = "Text of the legend entry corresponding to this scatter object.";
+
+      case "linewidth"
+        s.doc = "Line width of the edge of the markers.";
+
+      case "marker"
+        s.doc = "@xref{XREFlinemarker, , @w{line marker property}}.";
+
+      case "markeredgealpha"
+        s.doc = "Transparency level of the faces of the markers where a \
+value of 0 means complete transparency and a value of 1 means solid faces \
+without transparency.  Note that the markers are not sorted from back to \
+front which might lead to unexpected results when rendering layered \
+transparent markers or in combination with other transparent objects.";
+        s.valid = "scalar";
+
+      case "markeredgecolor"
+        s.doc = "Color of the edge of the markers.  @qcode{\"none\"} means \
+that the edges are transparent and @qcode{\"flat\"} means that the value \
+from @code{cdata} is used.  @xref{XREFlinemarkeredgecolor, , \
+@w{line markeredgecolor property}}.";
+        s.valid = packopt ({markdef("@qcode{\"none\"}"), ...
+                            "@qcode{\"flat\"}", ...
+                            valid_color});
+
+      case "markerfacealpha"
+        s.doc = "Transparency level of the faces of the markers where a \
+value of 0 means complete transparency and a value of 1 means solid faces \
+without transparency.  Note that the markers are not sorted from back to \
+front which might lead to unexpected results when rendering layered \
+transparent markers or in combination with other transparent objects.";
+        s.valid = "scalar";
+
+      case "markerfacecolor"
+        s.doc = "Color of the face of the markers.  @qcode{\"none\"} means \
+that the faces are transparent, @qcode{\"flat\"} means that the value from \
+@code{cdata} is used, and @qcode{\"auto\"} uses the @code{color} property of \
+the ancestor axes. @xref{XREFlinemarkerfacecolor, , \
+@w{line markerfacecolor property}}.";
+        s.valid = packopt ({markdef("@qcode{\"none\"}"), ...
+                            "@qcode{\"flat\"}", ...
+                            "@qcode{\"auto\"}", ...
+                            valid_color});
+
+      case "seriesindex"
+      case "sizedatasource"
+      case "sizedata"
+        s.doc = "Size of the area of the marker. A scalar value applies to \
+all markers.  If @code{cdata} is an N-by-1 vector, it defines the color of \
+each one of the N scatter markers.";
+        s.valid =  packopt ({"[]", ...
+                             "scalar", ...
+                             "vector"});
+
+      case "xdatasource"
+      case "xdata"
+        s.valid = "vector";
+
+      case "ydatasource"
+      case "ydata"
+        s.valid = "vector";
+
+      case "zdatasource"
+      case "zdata"
+        s.valid = "vector";
+
+    endswitch
+
   ## Light properties
   elseif (strcmp (objname, "light"))
     switch (field)
@@ -1861,6 +1952,10 @@
     h = 0;
   elseif (strcmp (objname, "figure"))
     h = hf;
+  elseif (strcmp (objname, "scatter"))
+    ## Make sure to get a scatter object independent of graphics toolkit
+    hax = axes (hf);
+    h = __go_scatter__ (hax);
   else
     eval (["h = " objname " ();"]);
   endif
diff -r 6b6cb174d6bb -r 4835ea1a8c51 doc/interpreter/graphics_properties.mk
--- a/doc/interpreter/graphics_properties.mk	Tue May 05 01:10:39 2020 -0400
+++ b/doc/interpreter/graphics_properties.mk	Mon May 04 19:18:56 2020 +0200
@@ -5,6 +5,7 @@
   interpreter/plot-lineproperties.texi \
   interpreter/plot-patchproperties.texi \
   interpreter/plot-rootproperties.texi \
+  interpreter/plot-scatterproperties.texi \
   interpreter/plot-surfaceproperties.texi \
   interpreter/plot-textproperties.texi
 
@@ -35,5 +36,8 @@
 interpreter/plot-surfaceproperties.texi: interpreter/genpropdoc.m
 	$(AM_V_GEN)$(call gen-propdoc-texi,surface)
 
+interpreter/plot-scatterproperties.texi: interpreter/genpropdoc.m
+	$(AM_V_GEN)$(call gen-propdoc-texi,scatter)
+
 interpreter/plot-textproperties.texi: interpreter/genpropdoc.m
 	$(AM_V_GEN)$(call gen-propdoc-texi,text)
diff -r 6b6cb174d6bb -r 4835ea1a8c51 doc/interpreter/module.mk
--- a/doc/interpreter/module.mk	Tue May 05 01:10:39 2020 -0400
+++ b/doc/interpreter/module.mk	Mon May 04 19:18:56 2020 +0200
@@ -8,6 +8,7 @@
   %reldir%/plot-lineproperties.texi \
   %reldir%/plot-patchproperties.texi \
   %reldir%/plot-rootproperties.texi \
+  %reldir%/plot-scatterproperties.texi \
   %reldir%/plot-surfaceproperties.texi \
   %reldir%/plot-textproperties.texi \
   %reldir%/plot-uimenuproperties.texi \
@@ -51,6 +52,9 @@
 %reldir%/plot-rootproperties.texi: %reldir%/genpropdoc.m $(GRAPHICS_PROPS_SRC)
 	$(AM_V_GEN)$(call gen-propdoc-texi,root)
 
+%reldir%/plot-scatterproperties.texi: %reldir%/genpropdoc.m $(GRAPHICS_PROPS_SRC)
+	$(AM_V_GEN)$(call gen-propdoc-texi,scatter)
+
 %reldir%/plot-surfaceproperties.texi: %reldir%/genpropdoc.m $(GRAPHICS_PROPS_SRC)
 	$(AM_V_GEN)$(call gen-propdoc-texi,surface)
 
diff -r 6b6cb174d6bb -r 4835ea1a8c51 doc/interpreter/octave.texi
--- a/doc/interpreter/octave.texi	Tue May 05 01:10:39 2020 -0400
+++ b/doc/interpreter/octave.texi	Mon May 04 19:18:56 2020 +0200
@@ -569,6 +569,7 @@
 * Text Properties::
 * Image Properties::
 * Patch Properties::
+* Scatter Properties::
 * Surface Properties::
 * Light Properties::
 * Uimenu Properties::
@@ -600,7 +601,6 @@
 * Error Bar Series::
 * Line Series::
 * Quiver Group::
-* Scatter Group::
 * Stair Group::
 * Stem Series::
 * Surface Group::
diff -r 6b6cb174d6bb -r 4835ea1a8c51 doc/interpreter/plot.txi
--- a/doc/interpreter/plot.txi	Tue May 05 01:10:39 2020 -0400
+++ b/doc/interpreter/plot.txi	Mon May 04 19:18:56 2020 +0200
@@ -1183,11 +1183,12 @@
 and @code{plot3} return a handle pointing to an object of type line, the
 function @code{subplot} returns a handle pointing to an object of type axes,
 the function @code{fill} returns a handle pointing to an object of type patch,
-the functions @code{area}, @code{bar}, @code{barh}, @code{contour},
-@code{contourf}, @code{contour3}, @code{surf}, @code{mesh}, @code{surfc},
-@code{meshc}, @code{errorbar}, @code{quiver}, @code{quiver3}, @code{scatter},
-@code{scatter3}, @code{stair}, @code{stem}, @code{stem3} each return a handle
-to a complex data structure as documented in
+the function @code{scatter3} returns a handle to an object of type scatter,
+the functions @code{surf} and @code{mesh} each return a handle of type
+surface, the functions @code{area}, @code{bar}, @code{barh}, @code{contour},
+@code{contourf}, @code{contour3}, @code{surfc}, @code{meshc}, @code{errorbar},
+@code{quiver}, @code{quiver3}, @code{stair}, @code{stem}, @code{stem3} each
+return a handle to a complex data structure as documented in
 @ref{XREFdatasources,,Data Sources}.
 
 The graphics objects are arranged in a hierarchy:
@@ -1490,6 +1491,7 @@
 * Text Properties::
 * Image Properties::
 * Patch Properties::
+* Scatter Properties::
 * Surface Properties::
 * Light Properties::
 * Uimenu Properties::
@@ -1564,6 +1566,13 @@
 @include plot-patchproperties.texi
 
 
+@node Scatter Properties
+@subsubsection Scatter Properties
+@prindex @sortas{@ Scatter Properties} Scatter Properties
+
+@include plot-scatterproperties.texi
+
+
 @node Surface Properties
 @subsubsection Surface Properties
 @prindex @sortas{@ Surface Properties} Surface Properties
@@ -2120,7 +2129,6 @@
 * Error Bar Series::
 * Line Series::
 * Quiver Group::
-* Scatter Group::
 * Stair Group::
 * Stem Series::
 * Surface Group::
@@ -2446,46 +2454,6 @@
 Data source variables.
 @end table
 
-@node Scatter Group
-@subsubsection Scatter Group
-@cindex group objects
-@cindex scatter group
-
-Scatter series objects are created by the @code{scatter} or @code{scatter3}
-functions.  A single hggroup element contains as many children as there are
-points in the scatter plot, with each child representing one of the points.
-The properties of the stem series are
-
-@table @code
-@item linewidth
-The line width of the line objects of the points.  @xref{Line Styles}.
-
-@item  marker
-@itemx markeredgecolor
-@itemx markerfacecolor
-The line and fill color of the markers of the points.  @xref{Colors}.
-
-@item  xdata
-@itemx ydata
-@itemx zdata
-The original x, y and z data of the stems.
-
-@item cdata
-The color data for the points of the plot.  Each point can have a separate
-color, or a unique color can be specified.
-
-@item sizedata
-The size data for the points of the plot.  Each point can its own size or a
-unique size can be specified.
-
-@item  xdatasource
-@itemx ydatasource
-@itemx zdatasource
-@itemx cdatasource
-@itemx sizedatasource
-Data source variables.
-@end table
-
 @node Stair Group
 @subsubsection Stair Group
 @cindex group objects
diff -r 6b6cb174d6bb -r 4835ea1a8c51 libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc	Tue May 05 01:10:39 2020 -0400
+++ b/libinterp/corefcn/gl-render.cc	Mon May 04 19:18:56 2020 +0200
@@ -734,6 +734,8 @@
       draw_surface (dynamic_cast<const surface::properties&> (props));
     else if (go.isa ("patch"))
       draw_patch (dynamic_cast<const patch::properties&> (props));
+    else if (go.isa ("scatter"))
+      draw_scatter (dynamic_cast<const scatter::properties&> (props));
     else if (go.isa ("light"))
       draw_light (dynamic_cast<const light::properties&> (props));
     else if (go.isa ("hggroup"))
@@ -3695,6 +3697,116 @@
   }
 
   void
+  opengl_renderer::draw_scatter (const scatter::properties& props)
+  {
+#if defined (HAVE_OPENGL)
+
+    // Do not render if the scatter object has incoherent data
+    std::string msg;
+    if (props.has_bad_data (msg))
+      {
+        warning ("opengl_renderer: %s.  Not rendering.", msg.c_str ());
+        return;
+      }
+
+    bool draw_all = selecting;
+
+    if (draw_all || (! props.marker_is ("none")
+                     && ! (props.markeredgecolor_is ("none")
+                           && props.markerfacecolor_is ("none"))))
+      {
+        bool do_edge = draw_all || ! props.markeredgecolor_is ("none");
+        bool do_face = draw_all || ! props.markerfacecolor_is ("none");
+
+        const Matrix x = props.get_xdata ().matrix_value ();
+        const Matrix y = props.get_ydata ().matrix_value ();
+        const Matrix z = props.get_zdata ().matrix_value ();
+        const Matrix c = props.get_color_data ().matrix_value ();
+        const Matrix s = props.get_sizedata ().matrix_value ();
+
+        int np = x.rows ();
+        bool has_z = ! z.isempty ();
+
+        // If markeredgecolor is "flat", mecolor is empty
+        Matrix mecolor = (draw_all ? Matrix (1, 3, 0.0) :
+                          props.get_markeredgecolor_rgb ());
+        Matrix mfcolor = (draw_all ? Matrix (1, 3, 0.0) :
+                          props.get_markerfacecolor_rgb ());
+        const double mea = props.get_markeredgealpha ();
+        const double mfa = props.get_markerfacealpha ();
+
+        if (props.markerfacecolor_is ("auto"))
+          {
+            gh_manager& gh_mgr
+              = octave::__get_gh_manager__ ("opengl_renderer::draw_scatter");
+            graphics_object go = gh_mgr.get_object (props.get___myhandle__ ());
+            graphics_object ax = go.get_ancestor ("axes");
+            const axes::properties& ax_props
+              = dynamic_cast<const axes::properties&> (ax.get_properties ());
+
+            mfcolor = ax_props.get_color ().matrix_value ();
+          }
+
+        init_marker (props.get_marker (), std::sqrt (s(0)),
+                     props.get_linewidth ());
+
+        uint8_t clip_mask = (props.is_clipping () ? 0x7F : 0x40);
+        uint8_t clip_ok = 0x40;
+
+        Matrix cc;
+        if (! c.isempty ())
+          {
+            if (c.rows () == 1)
+              cc = c;
+            else
+              {
+                cc.resize (1, 3);
+                cc(0) = c(0,0);
+                cc(1) = c(0,1);
+                cc(2) = c(0,2);
+              }
+          }
+
+        for (int i = 0; i < np; i++)
+          {
+            if ((clip_code (x(i), y(i), (has_z ? z(i) : 0.0)) & clip_mask)
+                 != clip_ok)
+              continue;
+
+            if (c.rows () > 1)
+              {
+                cc(0) = c(i,0);
+                cc(1) = c(i,1);
+                cc(2) = c(i,2);
+              }
+
+            Matrix lc = (do_edge ? (mecolor.isempty () ? cc : mecolor)
+                                 : Matrix ());
+            Matrix fc = (do_face ? (mfcolor.isempty () ? cc : mfcolor)
+                                 : Matrix ());
+
+            if (s.numel () > 1)
+              change_marker (props.get_marker (), std::sqrt (s(i)));
+
+            draw_marker (x(i), y(i), (has_z ? z(i) : 0.0), lc, fc, mea, mfa);
+          }
+
+        end_marker ();
+      }
+
+#else
+
+    octave_unused_parameter (props);
+
+    // This shouldn't happen because construction of opengl_renderer
+    // objects is supposed to be impossible if OpenGL is not available.
+
+    panic_impossible ();
+
+#endif
+  }
+
+  void
   opengl_renderer::draw_light (const light::properties& props)
   {
 #if defined (HAVE_OPENGL)
@@ -4279,6 +4391,27 @@
   }
 
   void
+  opengl_renderer::change_marker (const std::string& m, double size)
+  {
+#if defined (HAVE_OPENGL)
+
+    marker_id = make_marker_list (m, size, false);
+    filled_marker_id = make_marker_list (m, size, true);
+
+#else
+
+    octave_unused_parameter (m);
+    octave_unused_parameter (size);
+
+    // This shouldn't happen because construction of opengl_renderer
+    // objects is supposed to be impossible if OpenGL is not available.
+
+    panic_impossible ();
+
+#endif
+  }
+
+  void
   opengl_renderer::end_marker (void)
   {
 #if defined (HAVE_OPENGL)
@@ -4304,7 +4437,8 @@
 
   void
   opengl_renderer::draw_marker (double x, double y, double z,
-                                const Matrix& lc, const Matrix& fc)
+                                const Matrix& lc, const Matrix& fc,
+                                const double la, const double fa)
   {
 #if defined (HAVE_OPENGL)
 
@@ -4315,12 +4449,12 @@
 
     if (filled_marker_id > 0 && fc.numel () > 0)
       {
-        m_glfcns.glColor3dv (fc.data ());
+        m_glfcns.glColor4d (fc(0), fc(1), fc(2), fa);
         set_polygon_offset (true, -1.0);
         m_glfcns.glCallList (filled_marker_id);
         if (lc.numel () > 0)
           {
-            m_glfcns.glColor3dv (lc.data ());
+            m_glfcns.glColor4d (lc(0), lc(1), lc(2), la);
             m_glfcns.glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
             m_glfcns.glEdgeFlag (GL_TRUE);
             set_polygon_offset (true, -2.0);
@@ -4331,7 +4465,7 @@
       }
     else if (marker_id > 0 && lc.numel () > 0)
       {
-        m_glfcns.glColor3dv (lc.data ());
+        m_glfcns.glColor4d (lc(0), lc(1), lc(2), la);
         m_glfcns.glCallList (marker_id);
       }
 
@@ -4342,6 +4476,8 @@
     octave_unused_parameter (z);
     octave_unused_parameter (lc);
     octave_unused_parameter (fc);
+    octave_unused_parameter (la);
+    octave_unused_parameter (fa);
 
     // This shouldn't happen because construction of opengl_renderer
     // objects is supposed to be impossible if OpenGL is not available.
diff -r 6b6cb174d6bb -r 4835ea1a8c51 libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h	Tue May 05 01:10:39 2020 -0400
+++ b/libinterp/corefcn/gl-render.h	Mon May 04 19:18:56 2020 +0200
@@ -81,6 +81,7 @@
     virtual void draw_line (const line::properties& props);
     virtual void draw_surface (const surface::properties& props);
     virtual void draw_patch (const patch::properties& props);
+    virtual void draw_scatter (const scatter::properties& props);
     virtual void draw_light (const light::properties& props);
     virtual void draw_hggroup (const hggroup::properties& props);
     virtual void draw_text (const text::properties& props);
@@ -115,9 +116,11 @@
     }
 
     virtual void init_marker (const std::string& m, double size, float width);
+    virtual void change_marker (const std::string& m, double size);
     virtual void end_marker (void);
     virtual void draw_marker (double x, double y, double z,
-                              const Matrix& lc, const Matrix& fc);
+                              const Matrix& lc, const Matrix& fc,
+                              const double la = 1.0, const double fa = 1.0);
 
     virtual void text_to_pixels (const std::string& txt,
                                  uint8NDArray& pixels,
diff -r 6b6cb174d6bb -r 4835ea1a8c51 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Tue May 05 01:10:39 2020 -0400
+++ b/libinterp/corefcn/graphics.cc	Mon May 04 19:18:56 2020 +0200
@@ -1157,8 +1157,9 @@
                 {
                   pfx = name.substr (0, 7);
 
-                  if (pfx.compare ("surface") || pfx.compare ("hggroup")
-                      || pfx.compare ("uipanel") || pfx.compare ("uitable"))
+                  if (pfx.compare ("surface") || pfx.compare ("scatter")
+                      || pfx.compare ("hggroup") || pfx.compare ("uipanel")
+                      || pfx.compare ("uitable"))
                     offset = 7;
                   else if (len >= 9)
                     {
@@ -1226,6 +1227,8 @@
     go = new light (h, p);
   else if (type.compare ("patch"))
     go = new patch (h, p);
+  else if (type.compare ("scatter"))
+    go = new scatter (h, p);
   else if (type.compare ("surface"))
     go = new surface (h, p);
   else if (type.compare ("hggroup"))
@@ -2297,8 +2300,9 @@
                 {
                   pfx = name.substr (0, 7);
 
-                  if (pfx.compare ("surface") || pfx.compare ("hggroup")
-                      || pfx.compare ("uipanel") || pfx.compare ("uitable"))
+                  if (pfx.compare ("surface") || pfx.compare ("scatter")
+                      || pfx.compare ("hggroup")|| pfx.compare ("uipanel")
+                      || pfx.compare ("uitable"))
                     offset = 7;
                   else if (len > 9)
                     {
@@ -2357,6 +2361,8 @@
             has_property = image::properties::has_core_property (pname);
           else if (pfx == "patch")
             has_property = patch::properties::has_core_property (pname);
+          else if (pfx == "scatter")
+            has_property = scatter::properties::has_core_property (pname);
           else if (pfx == "surface")
             has_property = surface::properties::has_core_property (pname);
           else if (pfx == "hggroup")
@@ -2439,8 +2445,9 @@
                 {
                   pfx = name.substr (0, 7);
 
-                  if (pfx.compare ("surface") || pfx.compare ("hggroup")
-                      || pfx.compare ("uipanel") || pfx.compare ("uitable"))
+                  if (pfx.compare ("surface") || pfx.compare ("scatter")
+                      || pfx.compare ("hggroup") || pfx.compare ("uipanel")
+                      || pfx.compare ("uitable"))
                     offset = 7;
                   else if (len > 9)
                     {
@@ -10222,6 +10229,55 @@
 // ---------------------------------------------------------------------
 
 octave_value
+scatter::properties::get_color_data (void) const
+{
+  octave_value c = get_cdata ();
+  if (c.is_undefined () || c.isempty ())
+    return Matrix ();
+  else
+    return convert_cdata (*this, c, c.columns () == 1, 2);
+}
+
+void
+scatter::properties::update_data (void)
+{
+  Matrix xd = get_xdata ().matrix_value ();
+  Matrix yd = get_ydata ().matrix_value ();
+  Matrix zd = get_zdata ().matrix_value ();
+  Matrix cd = get_cdata ().matrix_value ();
+  Matrix sd = get_sizedata ().matrix_value ();
+
+  bad_data_msg = "";
+  if (xd.dims () != yd.dims ()
+      || (xd.dims () != zd.dims () && ! zd.isempty ()))
+    {
+      bad_data_msg = "x/y/zdata must have the same dimensions";
+      return;
+    }
+
+  octave_idx_type x_rows = xd.rows ();
+  octave_idx_type c_cols = cd.columns ();
+  octave_idx_type c_rows = cd.rows ();
+
+  if (! cd.isempty () && (c_rows != 1 || c_cols != 3)
+      && (c_rows != x_rows || (c_cols != 1 && c_cols != 3)))
+    {
+      bad_data_msg = "cdata must be an rgb triplet or have the same number of "
+                     "rows as X and one or three columns";
+      return;
+    }
+
+  octave_idx_type s_rows = sd.rows ();
+  if (s_rows != 1 && s_rows != x_rows)
+    {
+      bad_data_msg = "sizedata must be a scalar or a vector with the same "
+                     "dimensions as X";
+      return;
+    }
+}
+// ---------------------------------------------------------------------
+
+octave_value
 surface::properties::get_color_data (void) const
 {
   return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
@@ -12445,6 +12501,7 @@
   plist_map["text"] = text::properties::factory_defaults ();
   plist_map["image"] = image::properties::factory_defaults ();
   plist_map["patch"] = patch::properties::factory_defaults ();
+  plist_map["scatter"] = scatter::properties::factory_defaults ();
   plist_map["surface"] = surface::properties::factory_defaults ();
   plist_map["light"] = light::properties::factory_defaults ();
   plist_map["hggroup"] = hggroup::properties::factory_defaults ();
@@ -13231,7 +13288,7 @@
 
   if (go.isa ("surface"))
     nd = 3;
-  else if ((go.isa ("line") || go.isa ("patch"))
+  else if ((go.isa ("line") || go.isa ("patch") || go.isa ("scatter"))
            && ! go.get ("zdata").isempty ())
     nd = 3;
   else
@@ -13334,6 +13391,15 @@
   GO_BODY (patch);
 }
 
+DEFMETHOD (__go_scatter__, interp, args, ,
+           doc: /* -*- texinfo -*-
+@deftypefn {} {} __go_scatter__ (@var{parent})
+Undocumented internal function.
+@end deftypefn */)
+{
+  GO_BODY (scatter);
+}
+
 DEFMETHOD (__go_light__, interp, args, ,
            doc: /* -*- texinfo -*-
 @deftypefn {} {} __go_light__ (@var{parent})
diff -r 6b6cb174d6bb -r 4835ea1a8c51 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Tue May 05 01:10:39 2020 -0400
+++ b/libinterp/corefcn/graphics.in.h	Mon May 04 19:18:56 2020 +0200
@@ -5054,6 +5054,209 @@
 
 // ---------------------------------------------------------------------
 
+class OCTINTERP_API scatter : public base_graphics_object
+{
+public:
+  class OCTINTERP_API properties : public base_properties
+  {
+  public:
+    octave_value get_color_data (void) const;
+
+    // Matlab allows incoherent data to be stored into patch properties.
+    // The patch should then be ignored by the renderer.
+    bool has_bad_data (std::string& msg) const
+    {
+      msg = bad_data_msg;
+      return ! msg.empty ();
+    }
+
+    bool is_aliminclude (void) const
+    { return aliminclude.is_on (); }
+    std::string get_aliminclude (void) const
+    { return aliminclude.current_value (); }
+
+    bool is_climinclude (void) const
+    { return climinclude.is_on (); }
+    std::string get_climinclude (void) const
+    { return climinclude.current_value (); }
+
+    // See the genprops.awk script for an explanation of the
+    // properties declarations.
+    // Programming note: Keep property list sorted if new ones are added.
+
+    BEGIN_PROPERTIES (scatter)
+      array_property annotation , Matrix ()
+      array_property cdata mu , Matrix ()
+      radio_property cdatamode l , "{auto}|manual"
+      string_property cdatasource , ""
+      array_property contextmenu , ""
+      array_property datatiptemplate , ""
+      string_property displayname , ""
+      array_property latitudedata , Matrix ()
+      string_property latitudedatasource , ""
+      double_property linewidth , 0.5
+      array_property longitudedata , Matrix ()
+      string_property longitudedatasource , ""
+      radio_property marker , "{o}|+|*|.|x|s|square|d|diamond|^|v|>|<|p|pentagram|h|hexagram|none"
+      double_property markeredgealpha , 1.0
+      color_property markeredgecolor , color_property (radio_values ("{flat}"), color_values (0, 0, 0))
+      double_property markerfacealpha , 1.0
+      color_property markerfacecolor , color_property (radio_values ("{none}|auto|flat"), color_values (0, 0, 0))
+      array_property rdata , Matrix ()
+      string_property rdatasource , ""
+      array_property seriesindex , Matrix ()
+      array_property sizedata u , Matrix ()
+      string_property sizedatasource , ""
+      array_property thetadata , Matrix ()
+      string_property thetadatasource , ""
+      array_property xdata u , Matrix ()
+      string_property xdatasource , ""
+      array_property ydata u , Matrix ()
+      string_property ydatasource , ""
+      array_property zdata u , Matrix ()
+      string_property zdatasource , ""
+
+      // hidden properties for limit computation
+      row_vector_property alim hlr , Matrix ()
+      row_vector_property clim hlr , Matrix ()
+      row_vector_property xlim hlr , Matrix ()
+      row_vector_property ylim hlr , Matrix ()
+      row_vector_property zlim hlr , Matrix ()
+      bool_property aliminclude hlg , "on"
+      bool_property climinclude hlg , "on"
+      bool_property xliminclude hl , "on"
+      bool_property yliminclude hl , "on"
+      bool_property zliminclude hl , "on"
+    END_PROPERTIES
+
+  protected:
+    void init (void)
+    {
+      xdata.add_constraint (dim_vector (-1, 1));
+      xdata.add_constraint (dim_vector (1, -1));
+      xdata.add_constraint (dim_vector (-1, 0));
+      xdata.add_constraint (dim_vector (0, -1));
+      ydata.add_constraint (dim_vector (-1, 1));
+      ydata.add_constraint (dim_vector (1, -1));
+      ydata.add_constraint (dim_vector (-1, 0));
+      ydata.add_constraint (dim_vector (0, -1));
+      zdata.add_constraint (dim_vector (-1, 1));
+      zdata.add_constraint (dim_vector (1, -1));
+      zdata.add_constraint (dim_vector (-1, 0));
+      zdata.add_constraint (dim_vector (0, -1));
+      cdata.add_constraint ("double");
+      cdata.add_constraint ("single");
+      cdata.add_constraint ("logical");
+      cdata.add_constraint ("int8");
+      cdata.add_constraint ("int16");
+      cdata.add_constraint ("int32");
+      cdata.add_constraint ("int64");
+      cdata.add_constraint ("uint8");
+      cdata.add_constraint ("uint16");
+      cdata.add_constraint ("uint32");
+      cdata.add_constraint ("uint64");
+      cdata.add_constraint ("real");
+      cdata.add_constraint (dim_vector (-1, 1));
+      cdata.add_constraint (dim_vector (-1, 3));
+      cdata.add_constraint (dim_vector (-1, 0));
+      cdata.add_constraint (dim_vector (0, -1));
+
+      linewidth.add_constraint ("min", 0.0, false);
+      sizedata.add_constraint ("min", 0.0, false);
+      sizedata.add_constraint (dim_vector (-1, 1));
+      sizedata.add_constraint (dim_vector (1, -1));
+      sizedata.add_constraint (dim_vector (-1, 0));
+      sizedata.add_constraint (dim_vector (0, -1));
+    }
+
+
+  private:
+    std::string bad_data_msg;
+
+    void update_xdata (void)
+    {
+      if (get_xdata ().isempty ())
+        {
+          // For compatibility with Matlab behavior,
+          // if x/ydata are set empty, silently empty other *data properties.
+          set_ydata (Matrix ());
+          set_zdata (Matrix ());
+          set_cdata (Matrix ());
+        }
+
+      set_xlim (xdata.get_limits ());
+
+      update_data ();
+    }
+
+    void update_ydata (void)
+    {
+      if (get_ydata ().isempty ())
+        {
+          set_xdata (Matrix ());
+          set_zdata (Matrix ());
+          set_cdata (Matrix ());
+        }
+
+      set_ylim (ydata.get_limits ());
+
+      update_data ();
+    }
+
+    void update_zdata (void)
+    {
+      set_zlim (zdata.get_limits ());
+
+      update_data ();
+    }
+
+    void update_cdata (void)
+    {
+      if (get_cdata ().matrix_value ().rows () == 1)
+        set_clim (cdata.get_limits ());
+      else
+        clim = cdata.get_limits ();
+
+      update_data ();
+    }
+
+    void update_sizedata (void)
+    {
+      update_data ();
+    }
+
+    void update_data (void);
+  };
+
+private:
+  properties xproperties;
+  property_list default_properties;
+
+public:
+  scatter (const graphics_handle& mh, const graphics_handle& p)
+    : base_graphics_object (), xproperties (mh, p)
+  { }
+
+  ~scatter (void) = default;
+
+  base_properties& get_properties (void) { return xproperties; }
+
+  const base_properties& get_properties (void) const { return xproperties; }
+
+  bool valid_object (void) const { return true; }
+
+  bool has_readonly_property (const caseless_str& pname) const
+  {
+    bool retval = xproperties.has_readonly_property (pname);
+    if (! retval)
+      retval = base_properties::has_readonly_property (pname);
+    return retval;
+  }
+
+};
+
+// ---------------------------------------------------------------------
+
 class OCTINTERP_API surface : public base_graphics_object
 {
 public:
diff -r 6b6cb174d6bb -r 4835ea1a8c51 scripts/plot/draw/private/__gnuplot_scatter__.m
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/private/__gnuplot_scatter__.m	Mon May 04 19:18:56 2020 +0200
@@ -0,0 +1,337 @@
+########################################################################
+##
+## Copyright (C) 2020 The Octave Project Developers
+##
+## See the file COPYRIGHT.md in the top-level directory of this
+## distribution or <https://octave.org/copyright/>.
+##
+## This file is part of Octave.
+##
+## Octave 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 3 of the License, or
+## (at your option) any later version.
+##
+## Octave 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 Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+##
+########################################################################
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{hg} =} __gnuplot_scatter__ (@dots{})
+## Undocumented internal function.
+## @end deftypefn
+
+function hg = __gnuplot_scatter__ (hax, fcn, x, y, z, c, s, marker, newargs)
+
+## Must occur after __next_line_color__ in order to work correctly.
+hg = hggroup (hax, "__appdata__", struct ("__creator__", "__scatter__"));
+newargs = __add_datasource__ (fcn, hg, {"x", "y", "z", "c", "size"},
+                              newargs{:});
+
+addproperty ("xdata", hg, "data", x);
+addproperty ("ydata", hg, "data", y);
+addproperty ("zdata", hg, "data", z);
+if (ischar (c))
+  ## For single explicit color, cdata is unused
+  addproperty ("cdata", hg, "data", []);
+else
+  addproperty ("cdata", hg, "data", c);
+endif
+addproperty ("sizedata", hg, "data", s);
+addlistener (hg, "xdata", @update_data);
+addlistener (hg, "ydata", @update_data);
+addlistener (hg, "zdata", @update_data);
+addlistener (hg, "cdata", @update_data);
+addlistener (hg, "sizedata", @update_data);
+
+one_explicit_color = ischar (c) || isequal (size (c), [1, 3]);
+s = sqrt (s);  # size adjustment for visual compatibility w/Matlab
+
+if (numel (x) <= 100)
+
+  ## For small number of points, we'll construct an object for each point.
+
+  if (numel (s) == 1)
+    s = repmat (s, numel (x), 1);
+  endif
+
+  if (one_explicit_color)
+    for i = 1 : numel (x)
+      if (filled)
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                      "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
+                      "faces", 1, "vertices", [x(i), y(i), z(i,:)],
+                      "marker", marker,  "markersize", s(i),
+                      "markeredgecolor", c, "markerfacecolor", c,
+                      "linestyle", "none");
+      else
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                      "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
+                      "faces", 1, "vertices", [x(i), y(i), z(i,:)],
+                      "marker", marker,  "markersize", s(i),
+                      "markeredgecolor", c, "markerfacecolor", "none",
+                      "linestyle", "none");
+      endif
+    endfor
+  else
+    if (rows (c) == 1)
+      c = repmat (c, rows (x), 1);
+    endif
+    for i = 1 : numel (x)
+      if (filled)
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                      "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
+                      "faces", 1, "vertices", [x(i), y(i), z(i,:)],
+                      "marker", marker, "markersize", s(i),
+                      "markeredgecolor", "none",
+                      "markerfacecolor", "flat",
+                      "cdata", c(i,:), "facevertexcdata", c(i,:),
+                      "linestyle", "none");
+      else
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                      "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
+                      "faces", 1, "vertices", [x(i), y(i), z(i,:)],
+                      "marker", marker, "markersize", s(i),
+                      "markeredgecolor", "flat",
+                      "markerfacecolor", "none",
+                      "cdata", c(i,:), "facevertexcdata", c(i,:),
+                      "linestyle", "none");
+      endif
+    endfor
+  endif
+
+else
+
+  ## For larger numbers of points, we use one single object.
+  vert = [x, y, z];
+  render_size_color (hg, vert, s, c, marker, filled, true);
+
+endif
+
+if (! ischar (c) && rows (c) > 1)
+  ax = get (hg, "parent");
+  clim = get (ax, "clim");
+  if (min (c(:)) < clim(1))
+    clim(1) = min (c(:));
+    set (ax, "clim", clim);
+  endif
+  if (max (c(:)) > clim(2))
+    set (ax, "clim", [clim(1), max(c(:))]);
+  endif
+endif
+
+addproperty ("linewidth", hg, "patchlinewidth", 0.5);
+addproperty ("marker", hg, "patchmarker", marker);
+if (filled)
+  addproperty ("markeredgecolor", hg, "patchmarkeredgecolor", "none");
+  if (one_explicit_color)
+    addproperty ("markerfacecolor", hg, "patchmarkerfacecolor", c);
+  else
+    addproperty ("markerfacecolor", hg, "patchmarkerfacecolor", "flat");
+  endif
+else
+  addproperty ("markerfacecolor", hg, "patchmarkerfacecolor", "none");
+  if (one_explicit_color)
+    addproperty ("markeredgecolor", hg, "patchmarkeredgecolor", c);
+  else
+    addproperty ("markeredgecolor", hg, "patchmarkeredgecolor", "flat");
+  endif
+endif
+addlistener (hg, "linewidth", @update_props);
+addlistener (hg, "marker", @update_props);
+addlistener (hg, "markerfacecolor", @update_props);
+addlistener (hg, "markeredgecolor", @update_props);
+
+## Matlab property, although Octave does not implement it.
+addproperty ("hittestarea", hg, "radio", "on|{off}", "off");
+
+if (! isempty (newargs))
+  set (hg, newargs{:});
+endif
+
+endfunction
+
+function render_size_color (hg, vert, s, c, marker, filled, isflat)
+
+  if (isscalar (s))
+    x = vert(:,1);
+    y = vert(:,2);
+    z = vert(:,3:end);
+    toolkit = get (ancestor (hg, "figure"), "__graphics_toolkit__");
+    ## Does gnuplot only support triangles with different vertex colors ?
+    ## FIXME: Verify gnuplot can only support one color.  If RGB triplets
+    ##        can be assigned to each vertex, then fix __gnuplot_draw_axes__.m
+    gnuplot_hack = (numel (x) > 1 && columns (c) == 3
+                    && strcmp (toolkit, "gnuplot"));
+    if (ischar (c) || ! isflat || gnuplot_hack)
+      if (filled)
+        ## "facecolor" and "edgecolor" must be set before any other properties
+        ## to skip co-planarity check (see bug #55751).
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                          "xdata", x, "ydata", y, "zdata", z,
+                          "faces", 1:numel (x), "vertices", vert,
+                          "marker", marker,
+                          "markeredgecolor", "none",
+                          "markerfacecolor", c(1,:),
+                          "markersize", s, "linestyle", "none");
+      else
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                          "xdata", x, "ydata", y, "zdata", z,
+                          "faces", 1:numel (x), "vertices", vert,
+                          "marker", marker,
+                          "markeredgecolor", c(1,:),
+                          "markerfacecolor", "none",
+                          "markersize", s, "linestyle", "none");
+      endif
+    else
+      if (filled)
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                          "xdata", x, "ydata", y, "zdata", z,
+                          "faces", 1:numel (x), "vertices", vert,
+                          "marker", marker, "markersize", s,
+                          "markeredgecolor", "none",
+                          "markerfacecolor", "flat",
+                          "cdata", c, "facevertexcdata", c,
+                          "linestyle", "none");
+      else
+        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
+                          "xdata", x, "ydata", y, "zdata", z,
+                          "faces", 1:numel (x), "vertices", vert,
+                          "marker", marker, "markersize", s,
+                          "markeredgecolor", "flat",
+                          "markerfacecolor", "none",
+                          "cdata", c, "facevertexcdata", c,
+                          "linestyle", "none");
+      endif
+    endif
+  else
+    ## Round size to one decimal place.
+    [ss, ~, s_to_ss] = unique (ceil (s*10) / 10);
+    for i = 1:rows (ss)
+      idx = (i == s_to_ss);
+      render_size_color (hg, vert(idx,:), ss(i), c,
+                             marker, filled, isflat);
+    endfor
+  endif
+
+endfunction
+
+function update_props (h, d)
+
+  lw = get (h, "linewidth");
+  m  = get (h, "marker");
+  fc = get (h, "markerfacecolor");
+  ec = get (h, "markeredgecolor");
+  kids = get (h, "children");
+
+  set (kids, "linewidth", lw, "marker", m,
+             "markerfacecolor", fc, "markeredgecolor", ec);
+
+endfunction
+
+## FIXME: This callback routine doesn't handle the case where N > 100.
+function update_data (h, d)
+
+  x = get (h, "xdata");
+  y = get (h, "ydata");
+  z = get (h, "zdata");
+  if (numel (x) > 100)
+    error ("scatter: cannot update data with more than 100 points.  Call scatter (x, y, ...) with new data instead.");
+  endif
+  c = get (h, "cdata");
+  one_explicit_color = ischar (c) || isequal (size (c), [1, 3]);
+  if (! one_explicit_color)
+    if (rows (c) == 1)
+      c = repmat (c, numel (x), 1);
+    endif
+  endif
+  filled = ! strcmp (get (h, "markerfacecolor"), "none");
+  s = get (h, "sizedata");
+  ## Size adjustment for visual compatibility with Matlab.
+  s = sqrt (s);
+  if (numel (s) == 1)
+    s = repmat (s, numel (x), 1);
+  endif
+  hlist = get (h, "children");
+
+  if (one_explicit_color)
+    if (filled)
+      if (isempty (z))
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", c, "markerfacecolor", c);
+
+        endfor
+      else
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i), z(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", c, "markerfacecolor", c);
+        endfor
+      endif
+    else
+      if (isempty (z))
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", c, "markerfacecolor", "none");
+
+        endfor
+      else
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i), z(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", c, "markerfacecolor", "none");
+        endfor
+      endif
+    endif
+  else
+    if (filled)
+      if (isempty (z))
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", "none", "markerfacecolor", "flat",
+                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
+                         "facevertexcdata", c(i,:));
+        endfor
+      else
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i), z(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", "none", "markerfacecolor", "flat",
+                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
+                         "facevertexcdata", c(i,:));
+        endfor
+      endif
+    else
+      if (isempty (z))
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", "flat", "markerfacecolor", "none",
+                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
+                         "facevertexcdata", c(i,:));
+        endfor
+      else
+        for i = 1 : length (hlist)
+          set (hlist(i), "vertices", [x(i), y(i), z(i)],
+                         "markersize", s(i),
+                         "markeredgecolor", "flat", "markerfacecolor", "none",
+                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
+                         "facevertexcdata", c(i,:));
+        endfor
+      endif
+    endif
+  endif
+
+endfunction
+
diff -r 6b6cb174d6bb -r 4835ea1a8c51 scripts/plot/draw/private/__scatter__.m
--- a/scripts/plot/draw/private/__scatter__.m	Tue May 05 01:10:39 2020 -0400
+++ b/scripts/plot/draw/private/__scatter__.m	Mon May 04 19:18:56 2020 +0200
@@ -24,13 +24,13 @@
 ########################################################################
 
 ## -*- texinfo -*-
-## @deftypefn {} {@var{hg} =} __scatter__ (@dots{})
+## @deftypefn {} {@var{hs} =} __scatter__ (@dots{})
 ## Undocumented internal function.
 ## @end deftypefn
 
-function hg = __scatter__ (varargin)
+function hs = __scatter__ (varargin)
 
-  hax = varargin{1};  # We don't do anything with this.  Could remove it.
+  hax = varargin{1};
   nd  = varargin{2};
   fcn = varargin{3};
   x   = varargin{4}(:);
@@ -173,307 +173,52 @@
     c = __next_line_color__ ();
   endif
 
-  ## Must occur after __next_line_color__ in order to work correctly.
-  hg = hggroup ("__appdata__", struct ("__creator__", "__scatter__"));
-  newargs = __add_datasource__ (fcn, hg, {"x", "y", "z", "c", "size"},
-                                newargs{:});
-
-  addproperty ("xdata", hg, "data", x);
-  addproperty ("ydata", hg, "data", y);
-  addproperty ("zdata", hg, "data", z);
-  if (ischar (c))
-    ## For single explicit color, cdata is unused
-    addproperty ("cdata", hg, "data", []);
-  else
-    addproperty ("cdata", hg, "data", c);
-  endif
-  addproperty ("sizedata", hg, "data", s);
-  addlistener (hg, "xdata", @update_data);
-  addlistener (hg, "ydata", @update_data);
-  addlistener (hg, "zdata", @update_data);
-  addlistener (hg, "cdata", @update_data);
-  addlistener (hg, "sizedata", @update_data);
-
-  one_explicit_color = ischar (c) || isequal (size (c), [1, 3]);
-  s = sqrt (s);  # size adjustment for visual compatibility w/Matlab
-
-  if (numel (x) <= 100)
-
-    ## For small number of points, we'll construct an object for each point.
-
-    if (numel (s) == 1)
-      s = repmat (s, numel (x), 1);
-    endif
-
-    if (one_explicit_color)
-      for i = 1 : numel (x)
-        if (filled)
-          __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                        "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
-                        "faces", 1, "vertices", [x(i), y(i), z(i,:)],
-                        "marker", marker,  "markersize", s(i),
-                        "markeredgecolor", c, "markerfacecolor", c,
-                        "linestyle", "none");
-        else
-          __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                        "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
-                        "faces", 1, "vertices", [x(i), y(i), z(i,:)],
-                        "marker", marker,  "markersize", s(i),
-                        "markeredgecolor", c, "markerfacecolor", "none",
-                        "linestyle", "none");
-        endif
-      endfor
-    else
-      if (rows (c) == 1)
-        c = repmat (c, rows (x), 1);
-      endif
-      for i = 1 : numel (x)
-        if (filled)
-          __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                        "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
-                        "faces", 1, "vertices", [x(i), y(i), z(i,:)],
-                        "marker", marker, "markersize", s(i),
-                        "markeredgecolor", "none",
-                        "markerfacecolor", "flat",
-                        "cdata", c(i,:), "facevertexcdata", c(i,:),
-                        "linestyle", "none");
-        else
-          __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                        "xdata", x(i), "ydata", y(i), "zdata", z(i,:),
-                        "faces", 1, "vertices", [x(i), y(i), z(i,:)],
-                        "marker", marker, "markersize", s(i),
-                        "markeredgecolor", "flat",
-                        "markerfacecolor", "none",
-                        "cdata", c(i,:), "facevertexcdata", c(i,:),
-                        "linestyle", "none");
-        endif
-      endfor
-    endif
+  if (strcmp ("gnuplot", graphics_toolkit ()))
+    ## Legacy code using patch for gnuplot toolkit
+    hs = __gnuplot_scatter__ (hax, fcn, x, y, z, c, s, marker, newargs)
 
   else
-
-    ## For larger numbers of points, we use one single object.
-    vert = [x, y, z];
-    render_size_color (hg, vert, s, c, marker, filled, true);
-
-  endif
-
-  if (! ischar (c) && rows (c) > 1)
-    ax = get (hg, "parent");
-    clim = get (ax, "clim");
-    if (min (c(:)) < clim(1))
-      clim(1) = min (c(:));
-      set (ax, "clim", clim);
+    ## Use OpenGL rendering for "qt" and "fltk" graphics toolkits
+    if isempty (x)
+      c = x;
+      s = x;
     endif
-    if (max (c(:)) > clim(2))
-      set (ax, "clim", [clim(1), max(c(:))]);
+    if ischar (c)
+      c = str2rgb (c);
     endif
-  endif
+    hs = __go_scatter__ (hax, "xdata", x(:), "ydata", y(:), "zdata", z(:),
+                         "cdata", c, "sizedata", s(:), "marker", marker,
+                         newargs{:});
 
-  addproperty ("linewidth", hg, "patchlinewidth", 0.5);
-  addproperty ("marker", hg, "patchmarker", marker);
-  if (filled)
-    addproperty ("markeredgecolor", hg, "patchmarkeredgecolor", "none");
-    if (one_explicit_color)
-      addproperty ("markerfacecolor", hg, "patchmarkerfacecolor", c);
-    else
-      addproperty ("markerfacecolor", hg, "patchmarkerfacecolor", "flat");
+    if filled
+      set (hs, "markerfacecolor", "flat");
     endif
-  else
-    addproperty ("markerfacecolor", hg, "patchmarkerfacecolor", "none");
-    if (one_explicit_color)
-      addproperty ("markeredgecolor", hg, "patchmarkeredgecolor", c);
-    else
-      addproperty ("markeredgecolor", hg, "patchmarkeredgecolor", "flat");
-    endif
-  endif
-  addlistener (hg, "linewidth", @update_props);
-  addlistener (hg, "marker", @update_props);
-  addlistener (hg, "markerfacecolor", @update_props);
-  addlistener (hg, "markeredgecolor", @update_props);
-
-  ## Matlab property, although Octave does not implement it.
-  addproperty ("hittestarea", hg, "radio", "on|{off}", "off");
-
-  if (! isempty (newargs))
-    set (hg, newargs{:});
   endif
 
 endfunction
 
-function render_size_color (hg, vert, s, c, marker, filled, isflat)
+
+function rgb = str2rgb(str)
+## convert a color code to the corresponding rgb values
+rgb = [];
 
-  if (isscalar (s))
-    x = vert(:,1);
-    y = vert(:,2);
-    z = vert(:,3:end);
-    toolkit = get (ancestor (hg, "figure"), "__graphics_toolkit__");
-    ## Does gnuplot only support triangles with different vertex colors ?
-    ## FIXME: Verify gnuplot can only support one color.  If RGB triplets
-    ##        can be assigned to each vertex, then fix __gnuplot_draw_axes__.m
-    gnuplot_hack = (numel (x) > 1 && columns (c) == 3
-                    && strcmp (toolkit, "gnuplot"));
-    if (ischar (c) || ! isflat || gnuplot_hack)
-      if (filled)
-        ## "facecolor" and "edgecolor" must be set before any other properties
-        ## to skip co-planarity check (see bug #55751).
-        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                          "xdata", x, "ydata", y, "zdata", z,
-                          "faces", 1:numel (x), "vertices", vert,
-                          "marker", marker,
-                          "markeredgecolor", "none",
-                          "markerfacecolor", c(1,:),
-                          "markersize", s, "linestyle", "none");
-      else
-        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                          "xdata", x, "ydata", y, "zdata", z,
-                          "faces", 1:numel (x), "vertices", vert,
-                          "marker", marker,
-                          "markeredgecolor", c(1,:),
-                          "markerfacecolor", "none",
-                          "markersize", s, "linestyle", "none");
-      endif
-    else
-      if (filled)
-        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                          "xdata", x, "ydata", y, "zdata", z,
-                          "faces", 1:numel (x), "vertices", vert,
-                          "marker", marker, "markersize", s,
-                          "markeredgecolor", "none",
-                          "markerfacecolor", "flat",
-                          "cdata", c, "facevertexcdata", c,
-                          "linestyle", "none");
-      else
-        __go_patch__ (hg, "facecolor", "none", "edgecolor", "none",
-                          "xdata", x, "ydata", y, "zdata", z,
-                          "faces", 1:numel (x), "vertices", vert,
-                          "marker", marker, "markersize", s,
-                          "markeredgecolor", "flat",
-                          "markerfacecolor", "none",
-                          "cdata", c, "facevertexcdata", c,
-                          "linestyle", "none");
-      endif
-    endif
-  else
-    ## Round size to one decimal place.
-    [ss, ~, s_to_ss] = unique (ceil (s*10) / 10);
-    for i = 1:rows (ss)
-      idx = (i == s_to_ss);
-      render_size_color (hg, vert(idx,:), ss(i), c,
-                             marker, filled, isflat);
-    endfor
-  endif
-
-endfunction
-
-function update_props (h, d)
-
-  lw = get (h, "linewidth");
-  m  = get (h, "marker");
-  fc = get (h, "markerfacecolor");
-  ec = get (h, "markeredgecolor");
-  kids = get (h, "children");
-
-  set (kids, "linewidth", lw, "marker", m,
-             "markerfacecolor", fc, "markeredgecolor", ec);
+switch str
+  case 'b'
+    rgb = [0, 0, 1];
+  case 'k'
+    rgb = [0, 0, 0];
+  case 'r'
+    rgb = [1, 0, 0];
+  case 'g'
+    rgb = [0, 1, 0];
+  case 'y'
+    rgb = [1, 1, 0];
+  case 'm'
+    rgb = [1, 0, 1];
+  case 'c'
+    rgb = [0, 1, 1];
+  case 'w'
+    rgb = [1, 1, 1];
+endswitch
 
 endfunction
-
-## FIXME: This callback routine doesn't handle the case where N > 100.
-function update_data (h, d)
-
-  x = get (h, "xdata");
-  y = get (h, "ydata");
-  z = get (h, "zdata");
-  if (numel (x) > 100)
-    error ("scatter: cannot update data with more than 100 points.  Call scatter (x, y, ...) with new data instead.");
-  endif
-  c = get (h, "cdata");
-  one_explicit_color = ischar (c) || isequal (size (c), [1, 3]);
-  if (! one_explicit_color)
-    if (rows (c) == 1)
-      c = repmat (c, numel (x), 1);
-    endif
-  endif
-  filled = ! strcmp (get (h, "markerfacecolor"), "none");
-  s = get (h, "sizedata");
-  ## Size adjustment for visual compatibility with Matlab.
-  s = sqrt (s);
-  if (numel (s) == 1)
-    s = repmat (s, numel (x), 1);
-  endif
-  hlist = get (h, "children");
-
-  if (one_explicit_color)
-    if (filled)
-      if (isempty (z))
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", c, "markerfacecolor", c);
-
-        endfor
-      else
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i), z(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", c, "markerfacecolor", c);
-        endfor
-      endif
-    else
-      if (isempty (z))
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", c, "markerfacecolor", "none");
-
-        endfor
-      else
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i), z(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", c, "markerfacecolor", "none");
-        endfor
-      endif
-    endif
-  else
-    if (filled)
-      if (isempty (z))
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", "none", "markerfacecolor", "flat",
-                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
-                         "facevertexcdata", c(i,:));
-        endfor
-      else
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i), z(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", "none", "markerfacecolor", "flat",
-                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
-                         "facevertexcdata", c(i,:));
-        endfor
-      endif
-    else
-      if (isempty (z))
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", "flat", "markerfacecolor", "none",
-                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
-                         "facevertexcdata", c(i,:));
-        endfor
-      else
-        for i = 1 : length (hlist)
-          set (hlist(i), "vertices", [x(i), y(i), z(i)],
-                         "markersize", s(i),
-                         "markeredgecolor", "flat", "markerfacecolor", "none",
-                         "cdata", reshape (c(i,:),[1, size(c)(2:end)]),
-                         "facevertexcdata", c(i,:));
-        endfor
-      endif
-    endif
-  endif
-
-endfunction
diff -r 6b6cb174d6bb -r 4835ea1a8c51 scripts/plot/draw/scatter.m
--- a/scripts/plot/draw/scatter.m	Tue May 05 01:10:39 2020 -0400
+++ b/scripts/plot/draw/scatter.m	Mon May 04 19:18:56 2020 +0200
@@ -72,6 +72,8 @@
 ## @end group
 ## @end example
 ##
+## Programming Note: The full list of properties is documented at
+## @ref{Scatter Properties}.
 ## @seealso{scatter3, patch, plot}
 ## @end deftypefn
 
@@ -219,3 +221,13 @@
 %!     title (str);
 %!   endfor
 %! endfor
+
+
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   hs = scatter ([], []);
+%!   assert (get (hs, "type"), "scatter");
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
diff -r 6b6cb174d6bb -r 4835ea1a8c51 scripts/plot/draw/scatter3.m
--- a/scripts/plot/draw/scatter3.m	Tue May 05 01:10:39 2020 -0400
+++ b/scripts/plot/draw/scatter3.m	Mon May 04 19:18:56 2020 +0200
@@ -59,7 +59,7 @@
 ## If the first argument @var{hax} is an axes handle, then plot into this axes,
 ## rather than the current axes returned by @code{gca}.
 ##
-## The optional return value @var{h} is a graphics handle to the hggroup
+## The optional return value @var{h} is a graphics handle to the scatter
 ## object representing the points.
 ##
 ## @example
@@ -69,6 +69,8 @@
 ## @end group
 ## @end example
 ##
+## Programming Note: The full list of properties is documented at
+## @ref{Scatter Properties}.
 ## @seealso{scatter, patch, plot}
 ## @end deftypefn
 
