# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1511267404 -3600
#      Tue Nov 21 13:30:04 2017 +0100
# Node ID 521a8ac9ff82495ca43cce3bca8ab19ac3c5ef62
# Parent  ad0ca13aa6e2509ad13fceff6c65bcdcec5cc567
Enforce axes ticks and limits to be in ascending order (bug #52412).

* graphics.in.h (row_vector_property): Add new property "special_constraints".

* graphics.cc (row_vector_property::validate): Add new overload to check for
ascending or descending order.

* graphics.cc (axes::properties::init): Add constraints to [xyz]lim and
[xyz]tick.

* __axis_limits__.m: Remove check for ascending order.

* [xyz]ticks.m: Adapt doc string. Remove comment.

diff -r ad0ca13aa6e2 -r 521a8ac9ff82 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Mon Nov 20 08:05:10 2017 -0500
+++ b/libinterp/corefcn/graphics.cc	Tue Nov 21 13:30:04 2017 +0100
@@ -1617,6 +1617,57 @@
 }
 
 bool
+row_vector_property::validate (const octave_value& v)
+{
+  bool xok = array_property::validate (v);
+
+  if (special_constraints.size () == 0)
+    return xok;
+
+  // Check row_vector special constraints.
+  if (special_constraints.find (ENFORCE_ASCENDING)
+      != special_constraints.end ())
+    {
+      NDArray v_mat = v.array_value ();
+      double v1 = v_mat(0);
+      for (octave_idx_type i = 1; i < v_mat.numel (); i++)
+        {
+          if (v1 > v_mat(i))
+            error (R"(set: "%s" must be in ascending order)",
+                   get_name ().c_str ());
+          v1 = v_mat(i);
+        }
+    }
+  if (special_constraints.find (ENFORCE_DESCENDING)
+      != special_constraints.end ())
+    {
+      NDArray v_mat = v.array_value ();
+      double v1 = v_mat(0);
+      for (octave_idx_type i = 1; i < v_mat.numel (); i++)
+        {
+          if (v1 < v_mat(i))
+            error (R"(set: "%s" must be in descending order)",
+                   get_name ().c_str ());
+          v1 = v_mat(i);
+        }
+    }
+
+  return xok;
+}
+
+/*
+## test checking for special constraint (bug #52412)
+%!error <must be in ascending order>
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   hax = axes ("parent", hf);
+%!   set (hax, "xlim", [1 -1]);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+*/
+
+bool
 handle_property::do_set (const octave_value& v)
 {
   // Users may want to use empty matrix to reset a handle property
@@ -4649,16 +4700,22 @@
   clim.add_constraint (NOT_NAN);
   xlim.add_constraint (2);
   xlim.add_constraint (NOT_NAN);
+  xlim.add_constraint (ENFORCE_ASCENDING);
   ylim.add_constraint (2);
   ylim.add_constraint (NOT_NAN);
+  ylim.add_constraint (ENFORCE_ASCENDING);
   zlim.add_constraint (2);
   zlim.add_constraint (NOT_NAN);
+  zlim.add_constraint (ENFORCE_ASCENDING);
   xtick.add_constraint (dim_vector (1, -1));
   xtick.add_constraint (FINITE);
+  xtick.add_constraint (ENFORCE_ASCENDING);
   ytick.add_constraint (dim_vector (1, -1));
   ytick.add_constraint (FINITE);
+  ytick.add_constraint (ENFORCE_ASCENDING);
   ztick.add_constraint (dim_vector (1, -1));
   ztick.add_constraint (FINITE);
+  ztick.add_constraint (ENFORCE_ASCENDING);
   ticklength.add_constraint (dim_vector (1, 2));
   Matrix vw (1, 2, 0);
   vw(1) = 90;
diff -r ad0ca13aa6e2 -r 521a8ac9ff82 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Mon Nov 20 08:05:10 2017 -0500
+++ b/libinterp/corefcn/graphics.in.h	Tue Nov 21 13:30:04 2017 +0100
@@ -1466,7 +1466,6 @@
     return false;
   }
 
-private:
   OCTINTERP_API bool validate (const octave_value& v);
 
   OCTINTERP_API bool is_equal (const octave_value& v) const;
@@ -1485,12 +1484,19 @@
   std::pair<double, bool> minval, maxval;
 };
 
+enum special_constraints_type
+{
+  ENFORCE_ASCENDING,
+  ENFORCE_DESCENDING
+};
+
 class row_vector_property : public array_property
 {
 public:
   row_vector_property (const std::string& nm, const graphics_handle& h,
                        const octave_value& m)
-    : array_property (nm, h, m)
+    : array_property (nm, h, m),
+      special_constraints ()
   {
     add_constraint (dim_vector (-1, 1));
     add_constraint (dim_vector (1, -1));
@@ -1498,7 +1504,8 @@
   }
 
   row_vector_property (const row_vector_property& p)
-    : array_property (p)
+    : array_property (p),
+      special_constraints ()
   {
     add_constraint (dim_vector (-1, 1));
     add_constraint (dim_vector (1, -1));
@@ -1525,6 +1532,12 @@
     array_property::add_constraint (type, val, inclusive);
   }
 
+  void add_constraint (const special_constraints_type special_constraint)
+  {
+    special_constraints.insert (special_constraint);
+  }
+
+
   void add_constraint (octave_idx_type len)
   {
     size_constraints.remove (dim_vector (1, -1));
@@ -1550,6 +1563,7 @@
     p->finite_constraint = finite_constraint;
     p->minval = minval;
     p->maxval = maxval;
+    p->special_constraints = special_constraints;
 
     return p;
   }
@@ -1557,24 +1571,43 @@
 protected:
   bool do_set (const octave_value& v)
   {
-    bool retval = array_property::do_set (v);
-
-    dim_vector dv = data.dims ();
-
+    bool is_changed;
+    octave_value tmp = (v.issparse () ? v.full_value () : v);
+
+    if (! validate (tmp))
+      error (R"(invalid value for row vector property "%s")",
+             get_name ().c_str ());
+
+    dim_vector dv = tmp.dims ();
     if (dv(0) > 1 && dv(1) == 1)
       {
-        int tmp = dv(0);
+        int tmp_dim = dv(0);
         dv(0) = dv(1);
-        dv(1) = tmp;
-
-        data = data.reshape (dv);
+        dv(1) = tmp_dim;
+
+        tmp = tmp.reshape (dv);
+
+        is_changed = true;
       }
 
-    return retval;
+    // FIXME: should we check for actual data change?
+    if (is_changed || ! is_equal (tmp))
+      {
+        data = tmp;
+
+        get_data_limits ();
+        return true;
+      }
+    else
+      return false;
+
   }
 
 private:
   OCTINTERP_API bool validate (const octave_value& v);
+
+  std::set<special_constraints_type> special_constraints;
+
 };
 
 // ---------------------------------------------------------------------
diff -r ad0ca13aa6e2 -r 521a8ac9ff82 scripts/plot/appearance/private/__axis_limits__.m
--- a/scripts/plot/appearance/private/__axis_limits__.m	Mon Nov 20 08:05:10 2017 -0500
+++ b/scripts/plot/appearance/private/__axis_limits__.m	Tue Nov 21 13:30:04 2017 +0100
@@ -44,8 +44,6 @@
     else
       if (! isnumeric (arg) && any (size (arg(:)) != [2, 1]))
         error ("%s: LIMITS must be a 2-element vector", fcn);
-      elseif (arg(1) >= arg(2))
-        error ("%s: axis limits must be increasing", fcn);
       endif
       set (hax, fcn, arg(:));
     endif
diff -r ad0ca13aa6e2 -r 521a8ac9ff82 scripts/plot/appearance/xticks.m
--- a/scripts/plot/appearance/xticks.m	Mon Nov 20 08:05:10 2017 -0500
+++ b/scripts/plot/appearance/xticks.m	Tue Nov 21 13:30:04 2017 +0100
@@ -28,7 +28,7 @@
 ## When called without an argument, return the current tick locations as
 ## specified in the @qcode{"xtick"} axes property.  These locations can be
 ## changed by calling @code{xticks} with a vector of tick values.  Note:
-## ascending order is not required.
+## ascending order is required.
 ##
 ## When called with argument @qcode{"mode"}, @code{xticks} returns the current
 ## value of the axes property @qcode{"xtickmode"}.  This property can be
@@ -85,8 +85,6 @@
     if (nargout > 0)
       error ("xticks: too many output arguments requested");
     endif
-    ## NOTE: Matlab errors if tick points are not in ascending order. Octave
-    ## permits out of order tick points, so error is not produced.
     set (hax, "xtick", arg);
 
   elseif (ischar (arg))
diff -r ad0ca13aa6e2 -r 521a8ac9ff82 scripts/plot/appearance/yticks.m
--- a/scripts/plot/appearance/yticks.m	Mon Nov 20 08:05:10 2017 -0500
+++ b/scripts/plot/appearance/yticks.m	Tue Nov 21 13:30:04 2017 +0100
@@ -28,7 +28,7 @@
 ## When called without an argument, return the current tick locations as
 ## specified in the @qcode{"ytick"} axes property.  These locations can be
 ## changed by calling @code{yticks} with a vector of tick values.  Note:
-## ascending order is not required.
+## ascending order is required.
 ##
 ## When called with argument @qcode{"mode"}, @code{yticks} returns the current
 ## value of the axes property @qcode{"ytickmode"}.  This property can be
@@ -85,8 +85,6 @@
     if (nargout > 0)
       error ("yticks: too many output arguments requested");
     else
-     ## NOTE: Matlab errors if tick points are not in ascending order. Octave
-     ## permits out of order tick points, so error is not produced.
      set (hax, "ytick", arg);
 
     endif
diff -r ad0ca13aa6e2 -r 521a8ac9ff82 scripts/plot/appearance/zticks.m
--- a/scripts/plot/appearance/zticks.m	Mon Nov 20 08:05:10 2017 -0500
+++ b/scripts/plot/appearance/zticks.m	Tue Nov 21 13:30:04 2017 +0100
@@ -28,7 +28,7 @@
 ## When called without an argument, return the current tick locations as
 ## specified in the @qcode{"ztick"} axes property.  These locations can be
 ## changed by calling @code{zticks} with a vector of tick values.  Note:
-## ascending order is not required.
+## ascending order is required.
 ##
 ## When called with argument @qcode{"mode"}, @code{zticks} returns the current
 ## value of the axes property @qcode{"ztickmode"}.  This property can be
@@ -85,8 +85,6 @@
     if (nargout > 0)
       error ("zticks: too many output arguments requested");
     endif
-    ## NOTE: Matlab errors if tick points are not in ascending order. Octave
-    ## permits out of order tick points, so error is not produced.
     set (hax, "ztick", arg);
 
   elseif (ischar (arg))
