changeset 28286:496735a910c1

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. * struct2hdl.m: Support loading scatter objects.
author Markus Mützel <markus.muetzel@gmx.de>
date Mon, 04 May 2020 19:18:56 +0200
parents fdaec2feeed3
children 4787d0034c79
files doc/interpreter/genpropdoc.m doc/interpreter/graphics_properties.mk doc/interpreter/module.mk doc/interpreter/octave.texi doc/interpreter/plot.txi libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h scripts/plot/draw/private/__scatter__.m scripts/plot/draw/scatter.m scripts/plot/draw/scatter3.m scripts/plot/util/struct2hdl.m
diffstat 13 files changed, 765 insertions(+), 375 deletions(-) [+]
line wrap: on
line diff
--- a/doc/interpreter/genpropdoc.m	Sun May 10 18:07:48 2020 +0200
+++ 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,117 @@
 
     endswitch
 
+  ## Scatter properties
+  elseif (strcmp (objname, "scatter"))
+    switch (field)
+      ## Overridden shared properties
+      case "children"
+        s.doc = doc_unused;
+
+      ## Specific properties
+      case "cdatamode"
+        s.doc = "If @code{cdatamode} is @qcode{\"auto\"}, @code{cdata} is set \
+to the color from the @code{colororder} of the ancestor axes corresponding to \
+the @code{seriesindex}.";
+
+      case "cdatasource"
+        s.doc = sprintf (doc_notimpl, "Data from workspace variables");
+
+      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"
+        s.doc = "Each scatter object in the same axes is asigned an \
+incrementing integer.  This corresponds to the index into the \
+@code{colororder} of the ancestor axes that is used if @code{cdatamode} is \
+set to @qcode{\"auto\"}.";
+
+      case "sizedatasource"
+        s.doc = sprintf (doc_notimpl, "Data from workspace variables");
+
+      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"
+        s.doc = sprintf (doc_notimpl, "Data from workspace variables");
+
+      case "xdata"
+        s.doc = "Vector with the x coordinates of the scatter object.";
+        s.valid = "vector";
+
+      case "ydatasource"
+        s.doc = sprintf (doc_notimpl, "Data from workspace variables");
+
+      case "ydata"
+        s.doc = "Vector with the y coordinates of the scatter object.";
+        s.valid = "vector";
+
+      case "zdatasource"
+        s.doc = sprintf (doc_notimpl, "Data from workspace variables");
+
+      case "zdata"
+        s.doc = "For 3D data, vector with the y coordinates of the scatter \
+object.";
+        s.valid = packopt ({"[]", "vector"});
+
+    endswitch
+
   ## Light properties
   elseif (strcmp (objname, "light"))
     switch (field)
@@ -1861,6 +1973,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
--- a/doc/interpreter/graphics_properties.mk	Sun May 10 18:07:48 2020 +0200
+++ 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)
--- a/doc/interpreter/module.mk	Sun May 10 18:07:48 2020 +0200
+++ 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)
 
--- a/doc/interpreter/octave.texi	Sun May 10 18:07:48 2020 +0200
+++ 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::
--- a/doc/interpreter/plot.txi	Sun May 10 18:07:48 2020 +0200
+++ b/doc/interpreter/plot.txi	Mon May 04 19:18:56 2020 +0200
@@ -1171,24 +1171,30 @@
 The graphics functions use pointers, which are of class graphics_handle, in
 order to address the data structures which control visual display.  A
 graphics handle may point to any one of a number of different base object
-types and these objects are the graphics data structures themselves.  The
+types.  These objects are the graphics data structures themselves.  The
 primitive graphic object types are: @code{figure}, @code{axes}, @code{line},
-@code{text}, @code{patch}, @code{surface}, @code{text}, @code{image}, and
-@code{light}.
-
-Each of these objects has a function by the same name, and, each of these
+@code{text}, @code{patch}, @code{scatter}, @code{surface}, @code{text},
+@code{image}, and @code{light}.
+
+Each of these objects has a function by the same name, and each of these
 functions returns a graphics handle pointing to an object of the corresponding
-type.  In addition there are several functions which operate on properties of
-the graphics objects and which also return handles: the functions @code{plot}
-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
-@ref{XREFdatasources,,Data Sources}.
+type.
+
+In addition, there are several functions which operate on properties of the
+graphics objects and which also return handles.  This includes but is not
+limited to the following functions:  The functions @code{plot} and @code{plot3}
+return a handle pointing to an object of type @code{line}.  The function
+@code{subplot} returns a handle pointing to an object of type @code{axes}.
+The functions @code{fill}, @code{trimesh}, and @code{trisurf} return a handle
+pointing to an object of type patch.  The function @code{scatter3} returns a
+handle to an object of type scatter.  The functions @code{slice}, @code{surf},
+@code{surfl}, @code{mesh}, @code{meshz}, @code{pcolor}, and @code{waterfall}
+each return a handle of type surface.  The function @code{camlight} returns a
+handle to an object of type light.  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:
 
@@ -1200,8 +1206,12 @@
 
 3. Below the @code{figure} objects are @code{axes} or @code{hggroup} objects.
 
-4. Below the @code{axes} objects are @code{line}, @code{text}, @code{patch},
-@code{surface}, @code{image}, and @code{light} objects.
+4. Below the @code{axes} or @code{hggroup} objects are @code{line},
+@code{text}, @code{patch}, @code{scatter}, @code{surface}, @code{image}, and
+@code{light} objects.
+
+It is possible to walk this hierarchical tree by querying the @qcode{"parent"}
+and @qcode{"children"} properties of the graphics objects.
 
 Graphics handles may be distinguished from function handles
 (@pxref{Function Handles}) by means of the function @code{ishghandle}.
@@ -1490,6 +1500,7 @@
 * Text Properties::
 * Image Properties::
 * Patch Properties::
+* Scatter Properties::
 * Surface Properties::
 * Light Properties::
 * Uimenu Properties::
@@ -1564,6 +1575,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 +2138,6 @@
 * Error Bar Series::
 * Line Series::
 * Quiver Group::
-* Scatter Group::
 * Stair Group::
 * Stem Series::
 * Surface Group::
@@ -2446,46 +2463,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
--- a/libinterp/corefcn/gl-render.cc	Sun May 10 18:07:48 2020 +0200
+++ 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.
--- a/libinterp/corefcn/gl-render.h	Sun May 10 18:07:48 2020 +0200
+++ 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,
--- a/libinterp/corefcn/graphics.cc	Sun May 10 18:07:48 2020 +0200
+++ 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,119 @@
 // ---------------------------------------------------------------------
 
 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;
+    }
+}
+
+static bool updating_scatter_cdata = false;
+
+void
+scatter::properties::update_color (void)
+{
+  if (updating_scatter_cdata)
+    return;
+
+  gh_manager& gh_mgr
+    = octave::__get_gh_manager__ ("scatter::properties::update_color");
+
+  graphics_object go = gh_mgr.get_object (get___myhandle__ ());
+
+  axes::properties& parent_axes_prop
+    = dynamic_cast<axes::properties&>
+        (go.get_ancestor ("axes").get_properties ());
+
+  Matrix color_order = parent_axes_prop.get_colororder ().matrix_value ();
+  Matrix series_idx = get_seriesindex ().matrix_value ();
+  octave_idx_type s = (static_cast<octave_idx_type> (series_idx(0)) - 1)
+                      % color_order.rows ();
+
+  Matrix color = Matrix (1, 3, 0.);
+  color(0) = color_order(s,0);
+  color(1) = color_order(s,1);
+  color(2) = color_order(s,2);
+
+  octave::unwind_protect frame;
+  frame.protect_var (updating_scatter_cdata);
+  updating_scatter_cdata = true;
+
+  set_cdata (color);
+  set_cdatamode ("auto");
+}
+
+void
+scatter::initialize (const graphics_object& go)
+{
+  base_graphics_object::initialize (go);
+
+  Matrix series_idx = xproperties.get_seriesindex ().matrix_value ();
+  if (series_idx.isempty ())
+    {
+      // Increment series index counter in parent axes
+      axes::properties& parent_axes_prop
+        = dynamic_cast<axes::properties&>
+            (go.get_ancestor ("axes").get_properties ());
+
+      if (! parent_axes_prop.nextplot_is ("add"))
+        parent_axes_prop.set_nextseriesindex (1);
+
+      series_idx.resize (1, 1);
+      series_idx(0) = parent_axes_prop.get_nextseriesindex ();
+      xproperties.set_seriesindex (series_idx);
+
+      parent_axes_prop.set_nextseriesindex
+        (parent_axes_prop.get_nextseriesindex () + 1);
+    }
+
+  if (xproperties.cdatamode_is ("auto"))
+    xproperties.update_color ();
+}
+
+// ---------------------------------------------------------------------
+
+octave_value
 surface::properties::get_color_data (void) const
 {
   return convert_cdata (*this, get_cdata (), cdatamapping_is ("scaled"), 3);
@@ -12445,6 +12565,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 +13352,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 +13455,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})
--- a/libinterp/corefcn/graphics.in.h	Sun May 10 18:07:48 2020 +0200
+++ b/libinterp/corefcn/graphics.in.h	Mon May 04 19:18:56 2020 +0200
@@ -5081,6 +5081,239 @@
 
 // ---------------------------------------------------------------------
 
+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 in scatter properties.
+    // The scatter object 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 u , "{auto}|manual"
+      string_property cdatasource , ""
+      array_property contextmenu , Matrix ()
+      array_property datatiptemplate , Matrix ()
+      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 u , 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));
+      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));
+      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);
+      seriesindex.add_constraint (dim_vector (1, 1));
+      seriesindex.add_constraint (dim_vector (-1, 0));
+      seriesindex.add_constraint (dim_vector (0, -1));
+    }
+
+  public:
+    void update_color (void);
+
+  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 ());
+          bool cdatamode_auto = cdatamode.is ("auto");
+          set_cdata (Matrix ());
+          if (cdatamode_auto)
+            set_cdatamode ("auto");
+        }
+
+      set_xlim (xdata.get_limits ());
+
+      update_data ();
+    }
+
+    void update_ydata (void)
+    {
+      if (get_ydata ().isempty ())
+        {
+          set_xdata (Matrix ());
+          set_zdata (Matrix ());
+          bool cdatamode_auto = cdatamode.is ("auto");
+          set_cdata (Matrix ());
+          if (cdatamode_auto)
+            set_cdatamode ("auto");
+        }
+
+      set_ylim (ydata.get_limits ());
+
+      update_data ();
+    }
+
+    void update_zdata (void)
+    {
+      set_zlim (zdata.get_limits ());
+
+      update_data ();
+    }
+
+    void update_sizedata (void)
+    {
+      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_cdatamode (void)
+    {
+      if (cdatamode.is ("auto"))
+        update_color ();
+    }
+
+    void update_seriesindex (void)
+    {
+      if (cdatamode.is ("auto"))
+        update_color ();
+    }
+
+    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)
+  {
+    // FIXME: seriesindex should increment by one each time a new scatter
+    // object is added to the axes.
+  }
+
+  ~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;
+  }
+
+protected:
+  void initialize (const graphics_object& go);
+
+};
+
+// ---------------------------------------------------------------------
+
 class OCTINTERP_API surface : public base_graphics_object
 {
 public:
--- a/scripts/plot/draw/private/__scatter__.m	Sun May 10 18:07:48 2020 +0200
+++ 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}(:);
@@ -169,311 +169,57 @@
     endif
   endwhile
 
-  if (isempty (c))
-    c = __next_line_color__ ();
-  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)
 
-  ## 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);
+    ## Use OpenGL rendering for "qt" and "fltk" graphics toolkits
+    if isempty (x)
+      c = x;
+    endif
+    if ischar (c)
+      c = str2rgb (c);
+    endif
+    if isempty (c)
+      cdata_args = {};
+    else
+      cdata_args = {"cdata", c};
     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
+    hs = __go_scatter__ (hax, "xdata", x(:), "ydata", y(:), "zdata", z(:),
+                         cdata_args{:}, "sizedata", s(:), "marker", 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);
-    endif
-    if (max (c(:)) > clim(2))
-      set (ax, "clim", [clim(1), max(c(:))]);
+    if filled
+      set (hs, "markerfacecolor", "flat");
     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)
+
+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
--- a/scripts/plot/draw/scatter.m	Sun May 10 18:07:48 2020 +0200
+++ 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,20 @@
 %!     title (str);
 %!   endfor
 %! endfor
+
+
+%!testif ; ! strcmp (graphics_toolkit (), "gnuplot")
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   hs = scatter ([], []);
+%!   assert (get (hs, "type"), "scatter");
+%!   assert (isempty (get (hs, "xdata")));
+%!   assert (isempty (get (hs, "ydata")));
+%!   assert (isempty (get (hs, "zdata")));
+%!   assert (get (hs, "cdata"), [0, 0.4470, 0.7410]);
+%!   assert (get (hs, "cdatamode"), "auto");
+%!   assert (get (hs, "sizedata"), 36);
+%!   assert (get (hs, "linewidth"), 0.5);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
--- a/scripts/plot/draw/scatter3.m	Sun May 10 18:07:48 2020 +0200
+++ 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
 
--- a/scripts/plot/util/struct2hdl.m	Sun May 10 18:07:48 2020 +0200
+++ b/scripts/plot/util/struct2hdl.m	Mon May 04 19:18:56 2020 +0200
@@ -47,7 +47,7 @@
 
   fields = {"handle", "type", "children", "properties", "special"};
   partypes = {"root", "figure", "axes", "hggroup"};
-  othertypes = {"line", "patch", "surface", "image", "text"};
+  othertypes = {"line", "patch", "scatter", "surface", "image", "text"};
   alltypes = [partypes othertypes];
 
   if (nargin > 3 || ! isstruct (s))
@@ -173,6 +173,8 @@
     h = createline (s, par);
   elseif (strcmp (s.type, "patch"))
     [h, s] = createpatch (s, par);
+  elseif (strcmp (s.type, "scatter"))
+    [h, s] = createscatter (s, par);
   elseif (strcmp (s.type, "text"))
     if (isfield (s.properties, "extent"))
       s.properties = rmfield (s.properties, "extent");
@@ -377,6 +379,24 @@
 
 endfunction
 
+function [h, sout] = createscatter (s, par)
+
+  if (isempty (s.properties.zdata))
+    ## 2D scatter
+    h = scatter (s.properties.xdata, s.properties.ydata);
+  else
+    ## 3D scatter
+    h = scatter3 (s.properties.xdata, s.properties.ydata, s.properties.zdata);
+  endif
+
+  set (h, "parent", par);
+  s.properties = rmfield (s.properties,
+                          {"xdata", "ydata", "zdata"});
+  addmissingprops (h, s.properties);
+  sout = s;
+
+endfunction
+
 function h = createtext (s, par)
   h = text ("parent", par);
   addmissingprops (h, s.properties);