changeset 7427:65f0a8ced9d2

[project @ 2008-01-28 22:42:18 by jwe]
author jwe
date Mon, 28 Jan 2008 22:44:46 +0000
parents b9df9abdffbb
children 0c11c6907c38
files src/ChangeLog src/genprops.awk src/graphics.cc src/graphics.h.in src/oct-stream.cc
diffstat 5 files changed, 804 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,49 @@
+2008-01-28  Michael Goffioul <michael.goffioul@gmail.com>
+
+	* genprops.awk: Add update ('u') modifier and document the
+	readonly ('r') modifier.
+	* graphics.h.in (class base_scaler, class lin_scaler, class
+	log_scaler, class scaler): New classes to make abstraction of the
+	axis scale.
+	(base_graphics_backend::get_screen_resolution,
+	graphics_backend::get_screen_resolution): New methods.
+	(axes::properties::sx, axes::properties::sy,
+	axes::properties::sz): New scaler fields.
+	(axes::properties::get_x_scaler, axes::properties::get_y_scaler,
+	axes::properties::get_z_scaler): New accessors.
+	(axes::properties::x_render, axes::properties::x_render_inv,
+	axes::properties::x_gl_mat1, axes::properties::x_gl_mat2,
+	axes::properties::x_zlim): New utility Matrix fields.
+	(axes::properties::get_boundingbox,
+	axes::properties::update_camera,
+	axes::properites::update_aspectratios,
+	axes::properties::update_transform,
+	axes::properties::update_xscale, axes::properties::update_yscale,
+	axes::properties::update_zscale, axes::properties::update_view,
+	axes::properties::update_xdir, axes::properties::update_ydir,
+	axes::properties::update_zdir): New updater methods.
+	(axes::properties::init): Initialize sx, sy, sz and x_zlim correctly.
+	(axes::properties::position): Use valid default position value.
+	(axes::properties::xscale, axes::properties::yscale,
+	axes::properties::zscale, axes::properties::xdir,
+	axes::properties::ydir, axes::properties::zdir,
+	axes::properties::view): Add updater ('u') property modifier.
+	* graphics.cc (default_axes_position, default_axes_outerposition):
+	New initializers.
+	(convert_position): New utility function to convert position
+	according to specified units.
+	(gnuplot_backend::get_screen_resolution): New method.
+	(axes::properties::set_defaults): Initilize recently added properties.
+	(xform_matrix, xform_vector, transform, xform_scale,
+	xform_translate, scale, translate, xform, normalize, dot, cross,
+	unit_cube, cam2xform, xform2cam): New inline transformation
+	utility functions.
+	(axes::properties::update_camera,
+	axes::properties::update_aspectratios,
+	axes::properties::get_boundingbox): New updater methods for
+	computing transformation matrices.
+	(axes::update_axis_limits): Update transformation data.
+
 2008-01-28  John W. Eaton  <jwe@octave.org>
 
 	* oct-stream.cc (BEGIN_CHAR_CLASS_CONVERSION): Handle width properly.
--- a/src/genprops.awk
+++ b/src/genprops.awk
@@ -108,6 +108,19 @@
 ##
 ##   h:  Make the property hidden
 ##
+##   r:  Make the property read-only. A read-only property is not
+##       settable from the global set (caseless_str, octave_value)
+##       method, but still has set_X accessor.
+##
+##   u:  The property has an updater method. This effectively add
+##       the line
+##
+##         update_NAME ();
+##
+##       to the type-specific set function. This line is added before
+##       any other update call (like those added by the 'l' or 'm'
+##       modifiers.
+##
 ## The 'o' and 'O' qualifiers are only useful when the the property type
 ## is something other than octave_value.
 
@@ -275,6 +288,8 @@
       {
         printf ("\n  {\n    if (! error_state)\n      {\n        %s = val;\n",
           name[i]);
+        if (updater[i])
+          printf ("        %s ();\n", updater[i]);
         if (limits[i])
           printf ("        update_axis_limits (\"%s\");\n", name[i]);
         if (mode[i])
@@ -465,6 +480,7 @@
     emit_get[idx] = "definition";
     emit_set[idx] = "definition";
     defval[idx] = "";
+    updater[idx] = "";
 ##    if (type[idx] == "octave_value")
 ##      emit_ov_set[idx] = "";
 ##    else
@@ -510,6 +526,11 @@
 	if (index (quals, "r"))
 	  readonly[idx] = 1;
 
+        ## There is an updater method that should be called
+        ## from the set method
+        if (index (quals, "u"))
+          updater[idx] = ("update_" name[idx]);
+
 ##        ## emmit an asignment set function
 ##        if (index (quals, "a"))
 ##          emit_ov_set[idx] = "assignment";
--- a/src/graphics.cc
+++ b/src/graphics.cc
@@ -142,6 +142,25 @@
   return retval;
 }
 
+static Matrix
+default_axes_position (void)
+{
+  Matrix m (1, 4, 0.0);
+  m(0) = 0.13;
+  m(1) = 0.11;
+  m(2) = 0.775;
+  m(3) = 0.815;
+  return m;
+}
+
+static Matrix
+default_axes_outerposition (void)
+{
+  Matrix m (1, 4, 0.0);
+  m(2) = m(3) = 1.0;
+  return m;
+}
+
 // NOTE: "cb" is passed by value, because "function_value" method
 //       is non-const; passing "cb" by const-reference is not
 //       possible
@@ -200,6 +219,90 @@
   END_INTERRUPT_WITH_EXCEPTIONS;
 }
 
+static Matrix
+convert_position (const Matrix& pos, const caseless_str& from_units,
+		  const caseless_str& to_units,
+		  const Matrix& parent_dim = Matrix (1, 2, 0.0),
+		  const graphics_backend& backend = graphics_backend ())
+{
+  Matrix retval (1, 4);
+  double res = 0;
+
+  if (from_units.compare ("pixels"))
+    retval = pos;
+  else if (from_units.compare ("normalized"))
+    {
+      retval(0) = pos(0) * parent_dim(0) + 1;
+      retval(1) = pos(1) * parent_dim(1) + 1;
+      retval(2) = pos(2) * parent_dim(0);
+      retval(3) = pos(3) * parent_dim(1);
+    }
+  else if (from_units.compare ("characters"))
+    {
+      // FIXME: implement this
+    }
+  else
+    {
+      res = backend.get_screen_resolution ();
+
+      double f = 0.0;
+
+      if (from_units.compare ("points"))
+	f = res / 72.0;
+      else if (from_units.compare ("inches"))
+	f = res;
+      else if (from_units.compare ("centimeters"))
+	f = res / 2.54;
+
+      if (f > 0)
+	{
+	  retval(0) = pos(0) * f + 1;
+	  retval(1) = pos(1) * f + 1;
+	  retval(2) = pos(2) * f;
+	  retval(3) = pos(3) * f;
+	}
+    }
+
+  if (! to_units.compare ("pixels"))
+    {
+      if (to_units.compare ("normalized"))
+	{
+	  retval(0) = (retval(0) - 1) / parent_dim(0);
+	  retval(1) = (retval(1) - 1) / parent_dim(1);
+	  retval(2) /= parent_dim(0);
+	  retval(3) /= parent_dim(1);
+	}
+      else if (to_units.compare ("characters"))
+	{
+	  // FIXME: implement this
+	}
+      else
+	{
+	  if (res <= 0)
+	    res = backend.get_screen_resolution ();
+
+	  double f = 0.0;
+
+	  if (to_units.compare ("points"))
+	    f = res / 72.0;
+	  else if (to_units.compare ("inches"))
+	    f = res;
+	  else if (to_units.compare ("centimeters"))
+	    f = res / 2.54;
+
+	  if (f > 0)
+	    {
+	      retval(0) = (retval(0) - 1) / f;
+	      retval(1) = (retval(1) - 1) / f;
+	      retval(2) /= f;
+	      retval(3) /= f;
+	    }
+	}
+    }
+
+  return retval;
+}
+
 // ---------------------------------------------------------------------
 
 radio_values::radio_values (const std::string& opt_string)
@@ -1208,7 +1311,13 @@
     }
 
   Matrix get_canvas_size (const graphics_handle&) const
-    { return Matrix (1, 2, 0.0); }
+    {
+      Matrix sz (1, 2, 0.0);
+      return sz;
+    }
+
+  double get_screen_resolution (void) const
+    { return 72.0; }
 };
 
 graphics_backend
@@ -1365,7 +1474,7 @@
 axes::properties::set_defaults (base_graphics_object& obj,
 				const std::string& mode)
 {
-  position = Matrix ();
+  position = default_axes_position ();
   title = graphics_handle ();
   box = "on";
   key = "off";
@@ -1424,6 +1533,34 @@
   yaxislocation = "left";
   xaxislocation = "bottom";
 
+  // Note: camera properties will be set through update_transform
+  camerapositionmode = "auto";
+  cameratargetmode = "auto";
+  cameraupvectormode = "auto";
+  cameraviewanglemode = "auto";
+  plotboxaspectratio = Matrix (1, 3, 1.0);
+  drawmode = "normal";
+  fontangle = "normal";
+  fontname = "Helvetica";
+  fontsize = 12;
+  fontunits = "points";
+  fontweight = "normal";
+  gridlinestyle = "-";
+  linestyleorder = "-";
+  linewidth = 0.5;
+  minorgridlinestyle = "-";
+  // Note: plotboxaspectratio will be set through update_aspectratiors
+  plotboxaspectratiomode = "auto";
+  projection = "orthographic";
+  tickdir = "in";
+  tickdirmode = "auto";
+  ticklength = Matrix (1, 2, 0.1);
+  tightinset = Matrix (1, 4, 0.0);
+
+  sx = "linear";
+  sy = "linear";
+  sz = "linear";
+
   Matrix tview (1, 2, 0.0);
   tview(1) = 90;
   view = tview;
@@ -1449,6 +1586,8 @@
 
   children = Matrix ();
 
+  update_transform ();
+
   override_defaults (obj);
 }
 
@@ -1514,6 +1653,421 @@
   gh_manager::free (zlabel.handle_value ());
 }
 
+inline Matrix
+xform_matrix (void)
+{
+  Matrix m (4, 4, 0.0);
+  for (int i = 0; i < 4; i++)
+    m(i,i) = 1;
+  return m;
+}
+
+inline ColumnVector
+xform_vector (void)
+{
+  ColumnVector v (4, 0.0);
+  v(3) = 1;
+  return v;
+}
+
+inline ColumnVector
+xform_vector (double x, double y, double z)
+{
+  ColumnVector v (4, 1.0);
+  v(0) = x; v(1) = y; v(2) = z;
+  return v;
+}
+
+inline ColumnVector
+transform (const Matrix& m, double x, double y, double z)
+{
+  return (m * xform_vector (x, y, z));
+}
+
+inline Matrix
+xform_scale (double x, double y, double z)
+{
+  Matrix m (4, 4, 0.0);
+  m(0,0) = x; m(1,1) = y; m(2,2) = z; m(3,3) = 1;
+  return m;
+}
+
+inline Matrix
+xform_translate (double x, double y, double z)
+{
+  Matrix m = xform_matrix ();
+  m(0,3) = x; m(1,3) = y; m(2,3) = z; m(3,3) = 1;
+  return m;
+}
+
+inline void
+scale (Matrix& m, double x, double y, double z)
+{
+  m = m * xform_scale (x, y, z);
+}
+
+inline void
+translate (Matrix& m, double x, double y, double z)
+{
+  m = m * xform_translate (x, y, z);
+}
+
+inline void
+xform (ColumnVector& v, const Matrix& m)
+{
+  v = m*v;
+}
+
+inline void
+scale (ColumnVector& v, double x, double y, double z)
+{
+  v(0) *= x;
+  v(1) *= y;
+  v(2) *= z;
+}
+
+inline void
+translate (ColumnVector& v, double x, double y, double z)
+{
+  v(0) += x;
+  v(1) += y;
+  v(2) += z;
+}
+
+inline void
+normalize (ColumnVector& v)
+{
+  double fact = 1.0/sqrt(v(0)*v(0)+v(1)*v(1)+v(2)*v(2));
+  scale (v, fact, fact, fact);
+}
+
+inline double
+dot (const ColumnVector& v1, const ColumnVector& v2)
+{
+  return (v1(0)*v2(0)+v1(1)*v2(1)+v1(2)*v2(2));
+}
+
+inline double
+norm (const ColumnVector& v)
+{
+  return sqrt (dot (v, v));
+}
+
+inline ColumnVector
+cross (const ColumnVector& v1, const ColumnVector& v2)
+{
+  ColumnVector r = xform_vector ();
+  r(0) = v1(1)*v2(2)-v1(2)*v2(1);
+  r(1) = v1(2)*v2(0)-v1(0)*v2(2);
+  r(2) = v1(0)*v2(1)-v1(1)*v2(0);
+  return r;
+}
+
+inline Matrix
+unit_cube (void)
+{
+  static double data[32] = {
+      0,0,0,1,
+      1,0,0,1,
+      0,1,0,1,
+      0,0,1,1,
+      1,1,0,1,
+      1,0,1,1,
+      0,1,1,1,
+      1,1,1,1};
+  Matrix m (4, 8);
+  memcpy (m.fortran_vec (), data, sizeof(double)*32);
+  return m;
+}
+
+inline ColumnVector
+cam2xform (const Array<double>& m)
+{
+  ColumnVector retval (4, 1.0);
+  memcpy (retval.fortran_vec (), m.fortran_vec (), sizeof(double)*3);
+  return retval;
+}
+
+inline RowVector
+xform2cam (const ColumnVector& v)
+{
+  return v.extract_n (0, 3).transpose ();
+}
+
+void
+axes::properties::update_camera (void)
+{
+  double xd = (xdir_is ("normal") ? 1 : -1);
+  double yd = (ydir_is ("normal") ? 1 : -1);
+  double zd = (zdir_is ("normal") ? 1 : -1);
+
+  Matrix xlim = sx.scale (get_xlim ().matrix_value ());
+  Matrix ylim = sy.scale (get_ylim ().matrix_value ());
+  Matrix zlim = sz.scale (get_zlim ().matrix_value ());
+
+  double xo = xlim(xd > 0 ? 0 : 1);
+  double yo = ylim(yd > 0 ? 0 : 1);
+  double zo = zlim(zd > 0 ? 0 : 1);
+  
+  Matrix pb  = get_plotboxaspectratio ().matrix_value ();
+  
+  bool autocam = (camerapositionmode_is ("auto")
+		  && cameratargetmode_is ("auto")
+	    	  && cameraupvectormode_is ("auto")
+		  && cameraviewanglemode_is ("auto"));
+  bool dowarp = (autocam && dataaspectratiomode_is("auto")
+		 && plotboxaspectratiomode_is ("auto"));
+
+  ColumnVector c_eye (xform_vector ());
+  ColumnVector c_center (xform_vector ());
+  ColumnVector c_upv (xform_vector ());
+  
+  if (cameratargetmode_is ("auto"))
+    {
+      c_center(0) = (xlim(0)+xlim(1))/2;
+      c_center(1) = (ylim(0)+ylim(1))/2;
+      c_center(2) = (zlim(0)+zlim(1))/2;
+
+      cameratarget = xform2cam (c_center);
+    }
+  else
+    c_center = cam2xform (get_cameratarget ().matrix_value ());
+  
+  if (camerapositionmode_is ("auto"))
+    {
+      Matrix view = get_view ().matrix_value ();
+      double az = view(0), el = view(1);
+      double d = 5*sqrt(pb(0)*pb(0)+pb(1)*pb(1)+pb(2)*pb(2));
+
+      if (el == 90 || el == -90)
+	c_eye(2) = d*signum(el);
+      else
+	{
+	  az *= M_PI/180.0;
+	  el *= M_PI/180.0;
+	  c_eye(0) = d*cos(el)*sin(az);
+	  c_eye(1) = -d*cos(el)*cos(az);
+	  c_eye(2) = d*sin(el);
+	}
+      c_eye(0) = c_eye(0)*(xlim(1)-xlim(0))/(xd*pb(0))+c_center(0);
+      c_eye(1) = c_eye(1)*(ylim(1)-ylim(0))/(yd*pb(1))+c_center(1);
+      c_eye(2) = c_eye(2)*(zlim(1)-zlim(0))/(zd*pb(2))+c_center(2);
+
+      cameraposition = xform2cam (c_eye);
+    }
+  else
+    c_eye = cam2xform (get_cameraposition ().matrix_value ());
+
+  if (cameraupvectormode_is ("auto"))
+    {
+      Matrix view = get_view ().matrix_value ();
+      double az = view(0), el = view(1);
+
+      if (el == 90 || el == -90)
+	{
+	  c_upv(0) = -sin(az*M_PI/180.0)*(xlim(1)-xlim(0))/pb(0);
+	  c_upv(1) = cos(az*M_PI/180.0)*(ylim(1)-ylim(0))/pb(1);
+	}
+      else
+	c_upv(2) = 1;
+
+      cameraupvector = xform2cam (c_upv);
+    }
+  else
+    c_upv = cam2xform (get_cameraupvector ().matrix_value ());
+
+  Matrix x_view = xform_matrix ();
+  Matrix x_projection = xform_matrix ();
+  Matrix x_viewport = xform_matrix ();
+  Matrix x_normrender = xform_matrix ();
+  Matrix x_pre = xform_matrix ();
+  
+  x_render = xform_matrix ();
+  x_render_inv = xform_matrix ();
+
+  scale (x_pre, pb(0), pb(1), pb(2));
+  translate (x_pre, -0.5, -0.5, -0.5);
+  scale (x_pre, xd/(xlim(1)-xlim(0)), yd/(ylim(1)-ylim(0)),
+	 zd/(zlim(1)-zlim(0)));
+  translate (x_pre, -xo, -yo, -zo);
+
+  xform (c_eye, x_pre);
+  xform (c_center, x_pre);
+  scale (c_upv, pb(0)/(xlim(1)-xlim(0)), pb(1)/(ylim(1)-ylim(0)), 
+	 pb(2)/(zlim(1)-zlim(0)));
+  translate (c_center, -c_eye(0), -c_eye(1), -c_eye(2));
+
+  ColumnVector F (c_center), f (F), UP (c_upv);
+  normalize (f);
+  normalize (UP);
+
+  if (abs (dot (f, UP)) > 1e-15)
+    {
+      double fa = 1/sqrt(1-f(2)*f(2));
+      scale (UP, fa, fa, fa);
+    }
+
+  ColumnVector s = cross (f, UP);
+  ColumnVector u = cross (s, f);
+
+  scale (x_view, 1, 1, -1);
+  Matrix l = xform_matrix ();
+  l(0,0) = s(0); l(0,1) = s(1); l(0,2) = s(2);
+  l(1,0) = u(0); l(1,1) = u(1); l(1,2) = u(2);
+  l(2,0) = -f(0); l(2,1) = -f(1); l(2,2) = -f(2);
+  x_view = x_view * l;
+  translate (x_view, -c_eye(0), -c_eye(1), -c_eye(2));
+  scale (x_view, pb(0), pb(1), pb(2));
+  translate (x_view, -0.5, -0.5, -0.5);
+
+  Matrix x_cube = x_view * unit_cube ();
+  ColumnVector cmin = x_cube.row_min (), cmax = x_cube.row_max ();
+  double xM = cmax(0)-cmin(0);
+  double yM = cmax(1)-cmin(1);
+
+  Matrix bb = get_boundingbox ();
+  Matrix cs = get_backend ().get_canvas_size (__myhandle__);
+  double fh = cs(1);
+
+  double v_angle;
+
+  if (cameraviewanglemode_is ("auto"))
+    {
+      double af;
+
+      // FIXME: Was this really needed? When compared to Matlab, it
+      // does not seem to be required. Need investigation with concrete
+      // backend to see results visually.
+      if (false && dowarp)
+        af = 1.0 / (xM > yM ? xM : yM);
+      else
+        {
+          if ((bb(2)/bb(3)) > (xM/yM))
+            af = 1.0 / yM;
+          else
+            af = 1.0 / xM;
+        }
+      v_angle = 2 * (180.0 / M_PI) * atan (1 / (2 * af * norm (F)));
+
+      cameraviewangle = v_angle;
+    }
+  else
+    v_angle = get_cameraviewangle ();
+
+  double pf = 1 / (2 * tan ((v_angle / 2) * M_PI / 180.0) * norm (F));
+  scale (x_projection, pf, pf, 1);
+
+  if (dowarp)
+    {
+      xM *= pf;
+      yM *= pf;
+      translate (x_viewport, bb(0)+bb(2)/2, fh-(bb(1)+bb(3)/2)+1, 0);
+      scale (x_viewport, bb(2)/xM, -bb(3)/yM, 1);
+    }
+  else
+    {
+      double pix = 1;
+      if (autocam)
+	{
+	  if ((bb(2)/bb(3)) > (xM/yM))
+	    pix = bb(3);
+	  else
+	    pix = bb(2);
+	}
+      else
+	pix = (bb(2) < bb(3) ? bb(2) : bb(3));
+      translate (x_viewport, bb(0)+bb(2)/2, fh-(bb(1)+bb(3)/2)+1, 0);
+      scale (x_viewport, pix, -pix, 1);
+    }
+
+  x_normrender = x_viewport * x_projection * x_view;
+
+  x_cube = x_normrender * unit_cube ();
+  cmin = x_cube.row_min ();
+  cmax = x_cube.row_max ();
+  x_zlim.resize (1, 2);
+  x_zlim(0) = cmin(2);
+  x_zlim(1) = cmax(2);
+
+  x_render = x_normrender;
+  scale (x_render, xd/(xlim(1)-xlim(0)), yd/(ylim(1)-ylim(0)),
+	 zd/(zlim(1)-zlim(0)));
+  translate (x_render, -xo, -yo, -zo);
+
+  x_viewtransform = x_view;
+  x_projectiontransform = x_projection;
+  x_viewporttransform = x_viewport;
+  x_normrendertransform = x_normrender;
+  x_rendertransform = x_render;
+
+  x_render_inv = x_render.inverse ();
+
+  // Note: these matrices are a slight modified version of the regular
+  // matrices, more suited for OpenGL rendering (x_gl_mat1 => light
+  // => x_gl_mat2)
+  x_gl_mat1 = x_view;
+  scale (x_gl_mat1, xd/(xlim(1)-xlim(0)), yd/(ylim(1)-ylim(0)),
+	 zd/(zlim(1)-zlim(0)));
+  translate (x_gl_mat1, -xo, -yo, -zo);
+  x_gl_mat2 = x_viewport * x_projection;
+}
+
+void
+axes::properties::update_aspectratios (void)
+{
+  Matrix xlim = get_xlim ().matrix_value ();
+  Matrix ylim = get_ylim ().matrix_value ();
+  Matrix zlim = get_zlim ().matrix_value ();
+
+  double dx = (xlim(1)-xlim(0));
+  double dy = (ylim(1)-ylim(0));
+  double dz = (zlim(1)-zlim(0));
+
+  if (dataaspectratiomode_is ("auto"))
+    {
+      double dmin = xmin (xmin (dx, dy), dz);
+      Matrix da (1, 3, 0.0);
+
+      da(0) = dx/dmin;
+      da(1) = dy/dmin;
+      da(2) = dz/dmin;
+
+      dataaspectratio = da;
+    }
+
+  if (plotboxaspectratiomode_is ("auto"))
+    {
+      if (dataaspectratiomode_is ("auto"))
+	plotboxaspectratio = Matrix (1, 3, 1.0);
+      else
+	{
+	  Matrix da = get_dataaspectratio ().matrix_value ();
+	  Matrix pba (1, 3, 0.0);
+
+	  pba(0) = dx/da(0);
+	  pba(1) = dy/da(1);
+	  pba(2) = dz/da(2);
+	}
+    }
+  
+  // FIXME: if plotboxaspectratiomode is "manual", limits
+  // and/or dataaspectratio might be adapted
+}
+
+Matrix
+axes::properties::get_boundingbox (void) const
+{
+  graphics_backend b = get_backend ();
+  Matrix pos;
+  
+  pos = convert_position (get_position ().matrix_value (), get_units (),
+			  "pixels", b.get_canvas_size (__myhandle__), b);
+  pos(0)--;
+  pos(1)--;
+
+  return pos;
+}
+
 octave_value
 axes::get_default (const caseless_str& name) const
 {
@@ -1778,6 +2332,8 @@
       break;
     }
 
+  xproperties.update_transform ();
+
   unwind_protect::run ();
 }
 
--- a/src/graphics.h.in
+++ b/src/graphics.h.in
@@ -182,6 +182,129 @@
 
 // ---------------------------------------------------------------------
 
+class base_scaler
+{
+public:
+  base_scaler (void) { }
+
+  virtual Matrix scale (const Matrix& m) const
+    {
+      error ("invalid axis scale");
+      return m;
+    }
+
+  virtual double scale (double d) const
+    {
+      error ("invalid axis scale");
+      return d;
+    }
+  
+  virtual double unscale (double d) const
+    {
+      error ("invalid axis scale");
+      return d;
+    }
+
+  virtual base_scaler* clone () const
+    { return new base_scaler (); }
+};
+
+class lin_scaler : public base_scaler
+{
+public:
+  lin_scaler (void) { }
+
+  Matrix scale (const Matrix& m) const { return m; }
+
+  double scale (double d) const { return d; }
+
+  double unscale (double d) const { return d; }
+
+  base_scaler* clone (void) const { return new lin_scaler (); }
+};
+
+class log_scaler : public base_scaler
+{
+public:
+  log_scaler (void) { }
+
+  Matrix scale (const Matrix& m) const
+    {
+      Matrix retval (m.rows (), m.cols ());
+      const double *d1 = m.fortran_vec ();
+      double *d2 = retval.fortran_vec ();
+
+      for (int i = 0; i < m.numel (); i++)
+	d2[i] = log10 (d1[i]);
+
+      return retval;
+    }
+
+  double scale (double d) const
+    { return log10 (d); }
+
+  double unscale (double d) const
+    { return pow (10.0, d); }
+
+  base_scaler* clone (void) const
+    { return new log_scaler (); }
+};
+
+class scaler
+{
+public:
+  scaler (void) : rep (new base_scaler ()) { }
+
+  scaler (const scaler& s) : rep (s.rep->clone()) { }
+
+  ~scaler (void) { delete rep; }
+
+  Matrix scale (const Matrix& m) const
+    { return rep->scale (m); }
+
+  double scale (double d) const
+    { return rep->scale (d); }
+
+  double unscale (double d) const
+    { return rep->unscale (d); }
+
+  scaler& operator = (const scaler& s)
+    {
+      if (rep)
+	{
+	  delete rep;
+	  rep = 0;
+	}
+
+      rep = s.rep->clone ();
+
+      return *this;
+    }
+
+  scaler& operator = (const std::string& s)
+    {
+      if (rep)
+	{
+	  delete rep;
+	  rep = 0;
+	}
+
+      if (s == "log")
+	rep = new log_scaler ();
+      else if (s == "linear")
+	rep = new lin_scaler ();
+      else
+	rep = new base_scaler ();
+
+      return *this;
+    }
+
+private:
+  base_scaler *rep;
+};
+
+// ---------------------------------------------------------------------
+
 class property;
 
 class base_property
@@ -989,6 +1112,12 @@
       return Matrix (1, 2, 0.0);
     }
 
+  virtual double get_screen_resolution (void) const
+    {
+      error ("get_screen_resolution: invalid graphics backend");
+      return -1;
+    }
+
 private:
   std::string name;
   int count;
@@ -1053,6 +1182,9 @@
   Matrix get_canvas_size (const graphics_handle& fh) const
     { return rep->get_canvas_size (fh); }
 
+  double get_screen_resolution (void) const
+    { return rep->get_screen_resolution (); }
+
   OCTINTERP_API static graphics_backend default_backend (void);
 
   static void register_backend (const graphics_backend& b)
@@ -2046,11 +2178,31 @@
 
     void delete_children (void);
 
+    const scaler& get_x_scaler (void) const { return sx; }
+    const scaler& get_y_scaler (void) const { return sy; }
+    const scaler& get_z_scaler (void) const { return sz; }
+
+    Matrix get_boundingbox (void) const;
+
+    void update_camera (void);
+    void update_aspectratios (void);
+    void update_transform (void)
+      {
+	update_aspectratios ();
+	update_camera ();
+      }
+
+  private:
+    scaler sx, sy, sz;
+    Matrix x_render, x_render_inv;
+    Matrix x_gl_mat1, x_gl_mat2;
+    Matrix x_zlim;
+
     // See the genprops.awk script for an explanation of the
     // properties declarations.
 
     BEGIN_PROPERTIES(axes)
-      array_property position , Matrix ()
+      array_property position , default_axes_position ()
       mutable handle_property title GSO , graphics_handle ()
       bool_property box , "on"
       bool_property key , "off"
@@ -2099,17 +2251,17 @@
       color_property xcolor , color_values (0, 0, 0)
       color_property ycolor , color_values (0, 0, 0)
       color_property zcolor , color_values (0, 0, 0)
-      radio_property xscale al , "{linear}|log"
-      radio_property yscale al , "{linear}|log"
-      radio_property zscale al , "{linear}|log"
-      radio_property xdir , "{normal}|reverse"
-      radio_property ydir , "{normal}|reverse"
-      radio_property zdir , "{normal}|reverse"
+      radio_property xscale alu , "{linear}|log"
+      radio_property yscale alu , "{linear}|log"
+      radio_property zscale alu , "{linear}|log"
+      radio_property xdir u , "{normal}|reverse"
+      radio_property ydir u , "{normal}|reverse"
+      radio_property zdir u , "{normal}|reverse"
       radio_property yaxislocation , "{left}|right|zero"
       radio_property xaxislocation , "{bottom}|top|zero"
-      array_property view , Matrix ()
+      array_property view u , Matrix ()
       radio_property nextplot , "add|replace_children|{replace}"
-      array_property outerposition , Matrix ()
+      array_property outerposition , default_axes_outerposition ()
       radio_property activepositionproperty , "{outerposition}|position"
       radio_property __colorbar__ h , "{none}|north|south|east|west|northoutside|southoutside|eastoutside|westoutside"
       color_property ambientlightcolor , color_values (1, 1, 1)
@@ -2179,7 +2331,23 @@
 	currentpoint.add_constraint (dim_vector (2, 3));
 	ticklength.add_constraint (dim_vector (1, 2));
 	tightinset.add_constraint (dim_vector (1, 4));
+
+	x_zlim.resize (1, 2);
+	sx = "linear";
+	sy = "linear";
+	sz = "linear";
       }
+
+  private:
+    void update_xscale (void) { sx = get_xscale (); }
+    void update_yscale (void) { sy = get_yscale (); }
+    void update_zscale (void) { sz = get_zscale (); }
+
+    void update_view (void) { update_camera (); }
+
+    void update_xdir (void) { update_camera (); }
+    void update_ydir (void) { update_camera (); }
+    void update_zdir (void) { update_camera (); }
   };
 
 private:
--- a/src/oct-stream.cc
+++ b/src/oct-stream.cc
@@ -1541,8 +1541,8 @@
   do \
     { \
       if (! width) \
-	width = INT_MAX;
-
+	width = INT_MAX; \
+ \
       std::ostringstream buf; \
  \
       std::string char_class = elt->char_class; \