# HG changeset patch # User Markus Mützel # Date 1588612736 -7200 # Node ID 496735a910c144f3a695b4df1b01172af65f3b78 # Parent fdaec2feeed3888e8852d41783a01c5ec96596de 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. diff -r fdaec2feeed3 -r 496735a910c1 doc/interpreter/genpropdoc.m --- 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 diff -r fdaec2feeed3 -r 496735a910c1 doc/interpreter/graphics_properties.mk --- 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) diff -r fdaec2feeed3 -r 496735a910c1 doc/interpreter/module.mk --- 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) diff -r fdaec2feeed3 -r 496735a910c1 doc/interpreter/octave.texi --- 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:: diff -r fdaec2feeed3 -r 496735a910c1 doc/interpreter/plot.txi --- 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 diff -r fdaec2feeed3 -r 496735a910c1 libinterp/corefcn/gl-render.cc --- 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 (props)); else if (go.isa ("patch")) draw_patch (dynamic_cast (props)); + else if (go.isa ("scatter")) + draw_scatter (dynamic_cast (props)); else if (go.isa ("light")) draw_light (dynamic_cast (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 (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 fdaec2feeed3 -r 496735a910c1 libinterp/corefcn/gl-render.h --- 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, diff -r fdaec2feeed3 -r 496735a910c1 libinterp/corefcn/graphics.cc --- 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 + (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 (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 + (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}) diff -r fdaec2feeed3 -r 496735a910c1 libinterp/corefcn/graphics.in.h --- 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: diff -r fdaec2feeed3 -r 496735a910c1 scripts/plot/draw/private/__scatter__.m --- 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 diff -r fdaec2feeed3 -r 496735a910c1 scripts/plot/draw/scatter.m --- 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 diff -r fdaec2feeed3 -r 496735a910c1 scripts/plot/draw/scatter3.m --- 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 diff -r fdaec2feeed3 -r 496735a910c1 scripts/plot/util/struct2hdl.m --- 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);