changeset 12369:e23f4609d558 release-3-4-x

Move axes labels and title positioning to axes::properties
author Konstantinos Poulios <logari81@gmail.com>
date Thu, 03 Feb 2011 19:30:13 +0100
parents 3458c5bba6e5
children 19ebbad32395
files src/ChangeLog src/gl-render.cc src/gl-render.h src/graphics.cc src/graphics.h.in src/txt-eng-ft.cc src/txt-eng-ft.h
diffstat 7 files changed, 394 insertions(+), 285 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,21 @@
+2011-02-03  Konstantinos Poulios  <logari81@googlemail.com>
+
+	* gl-render.h: (opengl_renderer::draw_axes_titles): Remove prototype.
+	* gl-render.cc: (opengl_renderer::draw_axes_titles): Remove function.
+	(opengl_renderer::draw_axes_x_grid,
+	opengl_renderer::draw_axes_y_grid,
+	opengl_renderer::draw_axes_z_grid): Remove calculation of axis label
+	positions.
+	* graphics.cc: (axes::properties::update_xlabel_position,
+	axes::properties::update_ylabel_position
+	axes::properties::update_zlabel_position
+	axes::properties::update_title_position): New functions calculating
+	label and title positions.
+	(axes::properties::get_ticklabel_extents): New function.
+	* graphics.h.in: Provide functions prototypes.
+	* txt-eng-ft.cc (ft_render::get_extent): New function version.
+	* txt-eng-ft.h (ft_render::get_extent): Provide function prototype.
+
 2011-02-03  Kai Habel  <kai.habel@gmx.de>
 
 	* src/gl-render.cc(text_to_pixels): Use text_renderer object only
--- a/src/gl-render.cc
+++ b/src/gl-render.cc
@@ -1027,76 +1027,7 @@
                               0., 0, (box && xstate != AXE_ANY_DIR));
         }
 
-      text::properties& xlabel_props =
-        reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_xlabel ()).get_properties ());
-
-      xlabel_props.set_visible ("on");
-
-      if (! xlabel_props.get_string ().empty ())
-        {
-          if (xlabel_props.horizontalalignmentmode_is ("auto"))
-            {
-              xlabel_props.set_horizontalalignment
-                (xstate > AXE_DEPTH_DIR
-                 ? "center" : (xyzSym ? "left" : "right"));
-
-              xlabel_props.set_horizontalalignmentmode ("auto");
-            }
-
-          if (xlabel_props.verticalalignmentmode_is ("auto"))
-            {
-              xlabel_props.set_verticalalignment
-                (xstate == AXE_VERT_DIR || x2Dtop ? "bottom" : "top");
-
-              xlabel_props.set_verticalalignmentmode ("auto");
-            }
-
-          if (xlabel_props.positionmode_is ("auto")
-              || xlabel_props.rotationmode_is ("auto"))
-            {
-              double angle = 0;
-              ColumnVector p
-                = graphics_xform::xform_vector ((x_min+x_max)/2,
-                                                ypTick, zpTick);
-
-              if (tick_along_z)
-                p(2) += (signum(zpTick-zpTickN)*fz*xtickoffset);
-              else
-                p(1) += (signum(ypTick-ypTickN)*fy*xtickoffset);
-
-              p = xform.transform (p(0), p(1), p(2), false);
-
-              switch (xstate)
-                {
-                  case AXE_ANY_DIR:
-                    p(0) += (xyzSym ? wmax : -wmax);
-                    p(1) += hmax;
-                    break;
-
-                  case AXE_VERT_DIR:
-                    p(0) -= wmax;
-                    angle = 90;
-                    break;
-
-                  case AXE_HORZ_DIR:
-                    p(1) += (x2Dtop ? -hmax : hmax);
-                    break;
-                }
-
-              if (xlabel_props.positionmode_is ("auto"))
-                {
-                  p = xform.untransform (p(0), p(1), p(2), true);
-                  xlabel_props.set_position (p.extract_n (0, 3).transpose ());
-                  xlabel_props.set_positionmode ("auto");
-                }
-
-              if (xlabel_props.rotationmode_is ("auto"))
-                {
-                  xlabel_props.set_rotation (angle);
-                  xlabel_props.set_rotationmode ("auto");
-                }
-            }
-        }
+      gh_manager::get_object (props.get_xlabel ()).set ("visible", "on");
     }
   else
     gh_manager::get_object (props.get_xlabel ()).set ("visible", "off");
@@ -1200,74 +1131,7 @@
                               0., 0., 1, (box && ystate != AXE_ANY_DIR));
         }
 
-      text::properties& ylabel_props =
-        reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_ylabel ()).get_properties ());
-
-      ylabel_props.set_visible ("on");
-
-      if (! ylabel_props.get_string ().empty ())
-        {
-          if (ylabel_props.horizontalalignmentmode_is ("auto"))
-            {
-              ylabel_props.set_horizontalalignment
-                (ystate > AXE_DEPTH_DIR
-                 ? "center" : (!xyzSym ? "left" : "right"));
-
-              ylabel_props.set_horizontalalignmentmode ("auto");
-            }
-
-          if (ylabel_props.verticalalignmentmode_is ("auto"))
-            {
-              ylabel_props.set_verticalalignment
-                (ystate == AXE_VERT_DIR && !y2Dright ? "bottom" : "top");
-
-              ylabel_props.set_verticalalignmentmode ("auto");
-            }
-
-          if (ylabel_props.positionmode_is ("auto")
-              || ylabel_props.rotationmode_is ("auto"))
-            {
-              double angle = 0;
-              ColumnVector p = graphics_xform::xform_vector (xpTick, (y_min+y_max)/2, zpTick);
-
-              if (tick_along_z)
-                p(2) += (signum(zpTick-zpTickN)*fz*ytickoffset);
-              else
-                p(0) += (signum(xpTick-xpTickN)*fx*ytickoffset);
-
-              p = xform.transform (p(0), p(1), p(2), false);
-
-              switch (ystate)
-                {
-                  case AXE_ANY_DIR:
-                    p(0) += (!xyzSym ? wmax : -wmax);
-                    p(1) += hmax;
-                    break;
-
-                  case AXE_VERT_DIR:
-                    p(0) += (y2Dright ? wmax : -wmax);
-                    angle = 90;
-                    break;
-
-                  case AXE_HORZ_DIR:
-                    p(1) += hmax;
-                    break;
-                }
-
-              if (ylabel_props.positionmode_is ("auto"))
-                {
-                  p = xform.untransform (p(0), p(1), p(2), true);
-                  ylabel_props.set_position (p.extract_n (0, 3).transpose ());
-                  ylabel_props.set_positionmode ("auto");
-                }
-
-              if (ylabel_props.rotationmode_is ("auto"))
-                {
-                  ylabel_props.set_rotation (angle);
-                  ylabel_props.set_rotationmode ("auto");
-                }
-            }
-        }
+      gh_manager::get_object (props.get_ylabel ()).set ("visible", "on");
     }
   else
     gh_manager::get_object (props.get_ylabel ()).set ("visible", "off");
@@ -1406,157 +1270,13 @@
             }            
         }
 
-      text::properties& zlabel_props =
-        reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_zlabel ()).get_properties ());
-
-      zlabel_props.set_visible ("on");
-
-      if (! zlabel_props.get_string ().empty ())
-        {
-          bool camAuto = props.cameraupvectormode_is ("auto");
-
-          if (zlabel_props.horizontalalignmentmode_is ("auto"))
-            {
-              zlabel_props.set_horizontalalignment
-                ((zstate > AXE_DEPTH_DIR || camAuto) ? "center" : "right");
-
-              zlabel_props.set_horizontalalignmentmode ("auto");
-            }
-
-          if (zlabel_props.verticalalignmentmode_is ("auto"))
-            {
-              zlabel_props.set_verticalalignment
-                (zstate == AXE_VERT_DIR
-                 ? "bottom" : ((zSign || camAuto) ? "bottom" : "top"));
-
-              zlabel_props.set_verticalalignmentmode ("auto");
-            }
-
-          if (zlabel_props.positionmode_is ("auto")
-              || zlabel_props.rotationmode_is ("auto"))
-            {
-              double angle = 0;
-              ColumnVector p;
-
-              if (xySym)
-                {
-                  p = graphics_xform::xform_vector (xPlaneN, yPlane,
-                                                    (z_min+z_max)/2);
-                  if (xisinf (fy))
-                    p(0) += (signum(xPlaneN-xPlane)*fx*ztickoffset);
-                  else
-                    p(1) += (signum(yPlane-yPlaneN)*fy*ztickoffset);
-                }
-              else
-                {
-                  p = graphics_xform::xform_vector (xPlane, yPlaneN,
-                                                    (z_min+z_max)/2);
-                  if (xisinf (fx))
-                    p(1) += (signum(yPlaneN-yPlane)*fy*ztickoffset);
-                  else
-                    p(0) += (signum(xPlane-xPlaneN)*fx*ztickoffset);
-                }
-
-              p = xform.transform (p(0), p(1), p(2), false);
-
-              switch (zstate)
-                {
-                  case AXE_ANY_DIR:
-                    if (camAuto)
-                      {
-                        p(0) -= wmax;
-                        angle = 90;
-                      }
-
-                    // FIXME -- what's the correct offset?
-                    //
-                    //   p[0] += (!xySym ? wmax : -wmax);
-                    //   p[1] += (zSign ? hmax : -hmax);
-
-                    break;
-
-                  case AXE_VERT_DIR:
-                    p(0) -= wmax;
-                    angle = 90;
-                    break;
-
-                  case AXE_HORZ_DIR:
-                    p(1) += hmax;
-                    break;
-                }
-
-              if (zlabel_props.positionmode_is ("auto"))
-                {
-                  p = xform.untransform (p(0), p(1), p(2), true);
-                  zlabel_props.set_position (p.extract_n (0, 3).transpose ());
-                  zlabel_props.set_positionmode ("auto");
-                }
-
-              if (zlabel_props.rotationmode_is ("auto"))
-                {
-                  zlabel_props.set_rotation (angle);
-                  zlabel_props.set_rotationmode ("auto");
-                }
-            }
-        }
+      gh_manager::get_object (props.get_zlabel ()).set ("visible", "on");
     }
   else
     gh_manager::get_object (props.get_zlabel ()).set ("visible", "off");
 }
 
 void
-opengl_renderer::draw_axes_title (const axes::properties& props)
-{
-  double x_min = props.get_x_min ();
-  double x_max = props.get_x_max ();
-  double y_min = props.get_y_min ();
-  double y_max = props.get_y_max ();
-  double z_min = props.get_z_min ();
-  double z_max = props.get_z_max ();
-
-  // Title
-
-  // FIXME: bbox has to be moved to axes::properties
-  ColumnVector bbox(4);
-  bbox(0) = octave_Inf;
-  bbox(1) = octave_Inf;
-  bbox(2) = -octave_Inf;
-  bbox(3) = -octave_Inf;
-  for (int i = 0; i <= 1; i++)
-    for (int j = 0; j <= 1; j++)
-      for (int k = 0; k <= 1; k++)
-        {
-          ColumnVector p = xform.transform (i ? x_max : x_min,
-                                            j ? y_max : y_min,
-                                            k ? z_max : z_min, false);
-          bbox(0) = std::min (bbox(0), p(0));
-          bbox(1) = std::min (bbox(1), p(1));
-          bbox(2) = std::max (bbox(2), p(0));
-          bbox(3) = std::max (bbox(3), p(1));
-        }
-
-  bbox(2) = bbox(2)-bbox(0);
-  bbox(3) = bbox(3)-bbox(1);
-
-  Matrix x_zlim = props.get_transform_zlim ();
-
-  text::properties& title_props =
-    reinterpret_cast<text::properties&> (gh_manager::get_object (props.get_title ()).get_properties ());
-
-  if (! title_props.get_string ().empty ()
-      && title_props.positionmode_is ("auto"))
-    {
-      ColumnVector p = xform.untransform (bbox(0)+bbox(2)/2, (bbox(1)-10),
-                                          (x_zlim(0)+x_zlim(1))/2, true);
-
-      title_props.set_position (p.extract_n(0, 3).transpose ());
-      title_props.set_positionmode ("auto");
-    }
-
-  set_clipbox (x_min, x_max, y_min, y_max, z_min, z_max);
-}
-
-void
 opengl_renderer::draw_axes_children (const axes::properties& props)
 {
   // Children
@@ -1632,6 +1352,13 @@
 void
 opengl_renderer::draw_axes (const axes::properties& props)
 {
+  double x_min = props.get_x_min ();
+  double x_max = props.get_x_max ();
+  double y_min = props.get_y_min ();
+  double y_max = props.get_y_max ();
+  double z_min = props.get_z_min ();
+  double z_max = props.get_z_max ();
+
   setup_opengl_transformation (props);
 
   // draw axes object
@@ -1647,7 +1374,7 @@
 
   set_linestyle ("-");
 
-  draw_axes_title (props);
+  set_clipbox (x_min, x_max, y_min, y_max, z_min, z_max);
 
   draw_axes_children (props);
 }
--- a/src/gl-render.h
+++ b/src/gl-render.h
@@ -173,7 +173,6 @@
   void draw_axes_y_grid (const axes::properties& props);
   void draw_axes_z_grid (const axes::properties& props);
 
-  void draw_axes_title (const axes::properties& props);
   void draw_axes_children (const axes::properties& props);
 
 private:
--- a/src/graphics.cc
+++ b/src/graphics.cc
@@ -3301,6 +3301,7 @@
   xset (xlabel.handle_value (), "verticalalignmentmode", "auto");
   xset (xlabel.handle_value (), "clipping", "off");
   xset (xlabel.handle_value (), "color", get_xcolor ());
+  update_xlabel_position ();
 }
 
 void
@@ -3313,6 +3314,7 @@
   xset (ylabel.handle_value (), "verticalalignmentmode", "auto");
   xset (ylabel.handle_value (), "clipping", "off");
   xset (ylabel.handle_value (), "color", get_ycolor ());
+  update_ylabel_position ();
 }
 
 void
@@ -3325,6 +3327,7 @@
   xset (zlabel.handle_value (), "verticalalignmentmode", "auto");
   xset (zlabel.handle_value (), "clipping", "off");
   xset (zlabel.handle_value (), "color", get_zcolor ());
+  update_zlabel_position ();
 }
 
 void
@@ -3337,6 +3340,7 @@
   xset (title.handle_value (), "verticalalignment", "bottom");
   xset (title.handle_value (), "verticalalignmentmode", "auto");
   xset (title.handle_value (), "clipping", "off");
+  update_title_position ();
 }
 
 void
@@ -4063,6 +4067,305 @@
   xtickoffset = (mode2d ? std::max (0., xticklen) : std::abs (xticklen)) + 5;
   ytickoffset = (mode2d ? std::max (0., yticklen) : std::abs (yticklen)) + 5;
   ztickoffset = (mode2d ? std::max (0., zticklen) : std::abs (zticklen)) + 5;
+
+  update_xlabel_position ();
+  update_ylabel_position ();
+  update_zlabel_position ();
+  update_title_position ();
+}
+
+void
+axes::properties::update_xlabel_position (void)
+{
+  graphics_xform xform = get_transform ();
+
+  text::properties& xlabel_props = reinterpret_cast<text::properties&>
+    (gh_manager::get_object (get_xlabel ()).get_properties ());
+
+  if (xlabel_props.horizontalalignmentmode_is ("auto"))
+    {
+      xlabel_props.set_horizontalalignment
+        (xstate > AXE_DEPTH_DIR
+         ? "center" : (xyzSym ? "left" : "right"));
+
+      xlabel_props.set_horizontalalignmentmode ("auto");
+    }
+
+  if (xlabel_props.verticalalignmentmode_is ("auto"))
+    {
+      xlabel_props.set_verticalalignment
+        (xstate == AXE_VERT_DIR || x2Dtop ? "bottom" : "top");
+
+      xlabel_props.set_verticalalignmentmode ("auto");
+    }
+
+  if (xlabel_props.positionmode_is ("auto")
+      || xlabel_props.rotationmode_is ("auto"))
+    {
+      Matrix ext (1, 2, 0.0);
+      ext = get_ticklabel_extents (get_xtick ().matrix_value (),
+                                   get_xticklabel ().all_strings (),
+                                   get_xlim ().matrix_value ());
+
+      double wmax = ext(0), hmax = ext(1), angle = 0;
+      ColumnVector p =
+        graphics_xform::xform_vector ((xpTickN+xpTick)/2, ypTick, zpTick);
+
+      bool tick_along_z = nearhoriz || xisinf (fy);
+      if (tick_along_z)
+        p(2) += (signum(zpTick-zpTickN)*fz*xtickoffset);
+      else
+        p(1) += (signum(ypTick-ypTickN)*fy*xtickoffset);
+
+      p = xform.transform (p(0), p(1), p(2), false);
+
+      switch (xstate)
+        {
+          case AXE_ANY_DIR:
+            p(0) += (xyzSym ? wmax : -wmax);
+            p(1) += hmax;
+            break;
+
+          case AXE_VERT_DIR:
+            p(0) -= wmax;
+            angle = 90;
+            break;
+
+          case AXE_HORZ_DIR:
+            p(1) += (x2Dtop ? -hmax : hmax);
+            break;
+        }
+
+      if (xlabel_props.positionmode_is ("auto"))
+        {
+          p = xform.untransform (p(0), p(1), p(2), true);
+          xlabel_props.set_position (p.extract_n (0, 3).transpose ());
+          xlabel_props.set_positionmode ("auto");
+        }
+
+      if (xlabel_props.rotationmode_is ("auto"))
+        {
+          xlabel_props.set_rotation (angle);
+          xlabel_props.set_rotationmode ("auto");
+        }
+    }
+}
+
+void
+axes::properties::update_ylabel_position (void)
+{
+  graphics_xform xform = get_transform ();
+
+  text::properties& ylabel_props = reinterpret_cast<text::properties&>
+    (gh_manager::get_object (get_ylabel ()).get_properties ());
+
+  if (ylabel_props.horizontalalignmentmode_is ("auto"))
+	{
+	  ylabel_props.set_horizontalalignment
+		(ystate > AXE_DEPTH_DIR
+		 ? "center" : (!xyzSym ? "left" : "right"));
+
+	  ylabel_props.set_horizontalalignmentmode ("auto");
+	}
+
+  if (ylabel_props.verticalalignmentmode_is ("auto"))
+	{
+	  ylabel_props.set_verticalalignment
+		(ystate == AXE_VERT_DIR && !y2Dright ? "bottom" : "top");
+
+	  ylabel_props.set_verticalalignmentmode ("auto");
+	}
+
+  if (ylabel_props.positionmode_is ("auto")
+	  || ylabel_props.rotationmode_is ("auto"))
+	{
+      Matrix ext (1, 2, 0.0);
+      ext = get_ticklabel_extents (get_ytick ().matrix_value (),
+                                   get_yticklabel ().all_strings (),
+                                   get_ylim ().matrix_value ());
+
+      double wmax = ext(0), hmax = ext(1), angle = 0;
+	  ColumnVector p =
+	    graphics_xform::xform_vector (xpTick, (ypTickN+ypTick)/2, zpTick);
+
+      bool tick_along_z = nearhoriz || xisinf (fx);
+	  if (tick_along_z)
+		p(2) += (signum(zpTick-zpTickN)*fz*ytickoffset);
+	  else
+		p(0) += (signum(xpTick-xpTickN)*fx*ytickoffset);
+
+	  p = xform.transform (p(0), p(1), p(2), false);
+
+	  switch (ystate)
+		{
+		  case AXE_ANY_DIR:
+			p(0) += (!xyzSym ? wmax : -wmax);
+			p(1) += hmax;
+			break;
+
+		  case AXE_VERT_DIR:
+			p(0) += (y2Dright ? wmax : -wmax);
+			angle = 90;
+			break;
+
+		  case AXE_HORZ_DIR:
+			p(1) += hmax;
+			break;
+		}
+
+	  if (ylabel_props.positionmode_is ("auto"))
+		{
+		  p = xform.untransform (p(0), p(1), p(2), true);
+		  ylabel_props.set_position (p.extract_n (0, 3).transpose ());
+		  ylabel_props.set_positionmode ("auto");
+		}
+
+	  if (ylabel_props.rotationmode_is ("auto"))
+		{
+		  ylabel_props.set_rotation (angle);
+		  ylabel_props.set_rotationmode ("auto");
+		}
+	}
+}
+
+void
+axes::properties::update_zlabel_position (void)
+{
+  graphics_xform xform = get_transform ();
+
+  text::properties& zlabel_props = reinterpret_cast<text::properties&>
+    (gh_manager::get_object (get_zlabel ()).get_properties ());
+
+  bool camAuto = cameraupvectormode_is ("auto");
+
+  if (zlabel_props.horizontalalignmentmode_is ("auto"))
+	{
+	  zlabel_props.set_horizontalalignment
+		((zstate > AXE_DEPTH_DIR || camAuto) ? "center" : "right");
+
+	  zlabel_props.set_horizontalalignmentmode ("auto");
+	}
+
+  if (zlabel_props.verticalalignmentmode_is ("auto"))
+	{
+	  zlabel_props.set_verticalalignment
+		(zstate == AXE_VERT_DIR
+		 ? "bottom" : ((zSign || camAuto) ? "bottom" : "top"));
+
+	  zlabel_props.set_verticalalignmentmode ("auto");
+	}
+
+  if (zlabel_props.positionmode_is ("auto")
+	  || zlabel_props.rotationmode_is ("auto"))
+	{
+      Matrix ext (1, 2, 0.0);
+      ext = get_ticklabel_extents (get_ztick ().matrix_value (),
+                                   get_zticklabel ().all_strings (),
+                                   get_zlim ().matrix_value ());
+
+      double wmax = ext(0), hmax = ext(1), angle = 0;
+	  ColumnVector p;
+
+	  if (xySym)
+		{
+		  p = graphics_xform::xform_vector (xPlaneN, yPlane,
+											(zpTickN+zpTick)/2);
+		  if (xisinf (fy))
+			p(0) += (signum(xPlaneN-xPlane)*fx*ztickoffset);
+		  else
+			p(1) += (signum(yPlane-yPlaneN)*fy*ztickoffset);
+		}
+	  else
+		{
+		  p = graphics_xform::xform_vector (xPlane, yPlaneN,
+											(zpTickN+zpTick)/2);
+		  if (xisinf (fx))
+			p(1) += (signum(yPlaneN-yPlane)*fy*ztickoffset);
+		  else
+			p(0) += (signum(xPlane-xPlaneN)*fx*ztickoffset);
+		}
+
+	  p = xform.transform (p(0), p(1), p(2), false);
+
+	  switch (zstate)
+		{
+		  case AXE_ANY_DIR:
+			if (camAuto)
+			  {
+				p(0) -= wmax;
+				angle = 90;
+			  }
+
+			// FIXME -- what's the correct offset?
+			//
+			//   p[0] += (!xySym ? wmax : -wmax);
+			//   p[1] += (zSign ? hmax : -hmax);
+
+			break;
+
+		  case AXE_VERT_DIR:
+			p(0) -= wmax;
+			angle = 90;
+			break;
+
+		  case AXE_HORZ_DIR:
+			p(1) += hmax;
+			break;
+		}
+
+	  if (zlabel_props.positionmode_is ("auto"))
+		{
+		  p = xform.untransform (p(0), p(1), p(2), true);
+		  zlabel_props.set_position (p.extract_n (0, 3).transpose ());
+		  zlabel_props.set_positionmode ("auto");
+		}
+
+	  if (zlabel_props.rotationmode_is ("auto"))
+		{
+		  zlabel_props.set_rotation (angle);
+		  zlabel_props.set_rotationmode ("auto");
+		}
+	}
+}
+
+void
+axes::properties::update_title_position (void)
+{
+  graphics_xform xform = get_transform ();
+
+  text::properties& title_props = reinterpret_cast<text::properties&>
+    (gh_manager::get_object (get_title ()).get_properties ());
+
+  if (title_props.positionmode_is ("auto"))
+    {
+	  // FIXME: bbox should be stored in axes::properties
+	  ColumnVector bbox(4);
+	  bbox(0) = octave_Inf;
+	  bbox(1) = octave_Inf;
+	  bbox(2) = -octave_Inf;
+	  bbox(3) = -octave_Inf;
+	  for (int i = 0; i <= 1; i++)
+	    for (int j = 0; j <= 1; j++)
+	      for (int k = 0; k <= 1; k++)
+	        {
+	          ColumnVector p = xform.transform (i ? xPlaneN : xPlane,
+	                                            j ? yPlaneN : yPlane,
+	                                            k ? zPlaneN : zPlane, false);
+	          bbox(0) = std::min (bbox(0), p(0));
+	          bbox(1) = std::min (bbox(1), p(1));
+	          bbox(2) = std::max (bbox(2), p(0));
+	          bbox(3) = std::max (bbox(3), p(1));
+	        }
+	
+	  bbox(2) = bbox(2)-bbox(0);
+	  bbox(3) = bbox(3)-bbox(1);
+
+      ColumnVector p = xform.untransform (bbox(0)+bbox(2)/2, (bbox(1)-10),
+                                          (x_zlim(0)+x_zlim(1))/2, true);
+
+      title_props.set_position (p.extract_n(0, 3).transpose ());
+      title_props.set_positionmode ("auto");
+    }
 }
 
 static void
@@ -4637,6 +4940,48 @@
   labels = c;
 }
 
+Matrix
+axes::properties::get_ticklabel_extents (const Matrix& ticks,
+                                         const string_vector& ticklabels,
+                                         const Matrix& limits)
+{
+#ifdef HAVE_FREETYPE
+  //FIXME: text_renderer could be cached
+  ft_render text_renderer;
+  text_renderer.set_font (get ("fontname").string_value (),
+                          get ("fontweight").string_value (),
+                          get ("fontangle").string_value (),
+                          get ("fontsize").double_value ());
+#else
+  double fontsize = get ("fontsize").double_value ();
+#endif
+
+  Matrix ext (1, 2, 0.0);
+  double wmax = 0., hmax = 0.;
+  int n = std::min (ticklabels.numel (), ticks.numel ());
+  for (int i = 0; i < n; i++)
+    {
+      double val = ticks(i);
+      if (limits(0) <= val && val <= limits(1))
+        {
+#ifdef HAVE_FREETYPE
+          ext = text_renderer.get_extent (ticklabels(i));
+          wmax = std::max (wmax, ext(0));
+          hmax = std::max (hmax, ext(1));
+#else
+          //FIXME: find a better approximation
+          int len = ticklabels(i).length();
+          wmax = std::max (wmax, 0.5*fontsize*len);
+          hmax = fontsize;
+#endif
+        }
+    }
+
+  ext(0) = wmax;
+  ext(1) = hmax;
+  return ext;
+}
+
 void
 get_children_limits (double& min_val, double& max_val, double& min_pos,
                      const Matrix& kids, char limit_type)
--- a/src/graphics.h.in
+++ b/src/graphics.h.in
@@ -3311,6 +3311,11 @@
     void update_ydir (void) { update_camera (); update_axes_layout (); }
     void update_zdir (void) { update_camera (); update_axes_layout (); }
 
+    void update_xlabel_position (void);
+    void update_ylabel_position (void);
+    void update_zlabel_position (void);
+    void update_title_position (void);
+
     void update_ticklengths (void);
     void update_tickdir (void) { update_ticklengths (); }
     void update_tickdirmode (void) { update_ticklengths (); }
@@ -3380,6 +3385,10 @@
     void calc_ticks_and_lims (array_property& lims, array_property& ticks, array_property& mticks,
                               bool limmode_is_auto, bool is_logscale);
     void calc_ticklabels (const array_property& ticks, any_property& labels, bool is_logscale);
+    Matrix get_ticklabel_extents (const Matrix& ticks,
+                                  const string_vector& ticklabels,
+                                  const Matrix& limits);
+
     void fix_limits (array_property& lims)
     {
       if (lims.get ().is_empty ())
--- a/src/txt-eng-ft.cc
+++ b/src/txt-eng-ft.cc
@@ -474,6 +474,16 @@
   return extent;
 }
 
+Matrix
+ft_render::get_extent (const std::string& txt, double rotation)
+{
+  text_element *elt = text_parser_none ().parse (txt);
+  Matrix extent = get_extent (elt, rotation);
+  delete elt;
+  
+  return extent;
+}
+
 int
 ft_render::rotation_to_mode (double rotation) const
 {
--- a/src/txt-eng-ft.h
+++ b/src/txt-eng-ft.h
@@ -66,6 +66,7 @@
                        int rotation = ROTATION_0);
 
   Matrix get_extent (text_element *elt, double rotation = 0.0);
+  Matrix get_extent (const std::string& txt, double rotation = 0.0);
 
   void set_font (const std::string& name, const std::string& weight,
                  const std::string& angle, double size);