/***************************************************************************
**
**  This file is part of SciFigs.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2002-04-18
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "AxisWindow.h"
#include "AxisScaleProperties.h"
#include "AxisFormatProperties.h"
#include "Axis.h"
#include "SciFigsGlobal.h"

namespace SciFigs {

  const QString Axis::xmlAxisTag="Axis";

  /*!
    \class Axis Axis.h
    \brief The Axis widget provides the basic properties of an axis.

  The main properties are the minimum (minVal()) and maximum (maxVal()) values
  corresponding the end-points of the axis (expressed in real units).
  The axis support normal, log and inversed scales (scaleType()).
  The direction of the axis may be reversed (reversedScale()).
  position() manage the position of the axis relative to the GraphContent
  (left or right, and top or bottom).

  label() and tick() returns the interval for plotting the labels and the
  secondary ticks, respectively.

  numtype(), numprec(), numberPrecisionInversedScale(), title(), titleInversedScale(),
  and font() define the aspect of the axis title and of the labels.
  The labels may be defined by a list of names contained managed by stringLabels(),
  addStringLabel(), removeStringLabels(). In this case,
  labels are placed at integer values along the axis (0, 1, ...).
  The minimum and maximum values must be set accordingly.

  Conversions between real units and screen or media (printer, ps, image, ...)
  units are performed with r2s() and s2r().

  visMin() and visMax() return the current visible range if zooming is on.
  When no zoom is active, visMin() and visMax() are equal to minVal() and maxVal().
  The zoom can be disabled by using zoomEnabled() and setZoomEnabled().
  */

  /*!
    \fn double Axis::minimum() const
    Return the minimum value of the axis.

    \sa setMinimum(), setRange()
  */

  /*!
    \fn double Axis::maximum() const
    Return the maximum value of the axis.

    \sa setMaximum(), setRange()
  */

  /*!
    \fn void Axis::setMinimum(double val)
    Set the minimum value of the axis.
    It does not update the Axis structure. To commit changes see setVisibleRange()

    \sa maximum(), setMinimum(), setRange()
  */

  /*!
    \fn void Axis::setMaximum(double val)
    Set the maximum value of the axis.
    It does not update the Axis structure. To commit changes see setVisibleRange()

    \sa mniimum(), setMaximum(), setRange()
  */

  /*!
    \fn double Axis::visibleMinimum() const
    Return the minimum value of the zoom range.

    Use setVisibleRange() to modify it.
  */

  /*!
    \fn double Axis::visibleMaximum() const
    Return the maximum value of the zoom range.

    Use setVisibleRange() to modify it.
  */

  /*!
    \fn QVector<QString> * Axis::stringLabels() const
    Return the pointer to the current list of names for labels (null if no names
    are used).

    See addStringLabel() and removeStringLabels() to modify the names plotted in
    place of the normal axis values.
  */

  /*!
    \fn double Axis::majorTicks() const
    Return the interval for ploting labels and major ticks.

    Use setLabel() to modify it.
  */

  /*!
    \fn void Axis::setMajorTicks(double m)
    Set the interval for ploting labels and major ticks. For log scales, use only
    values less than 10.

    Use label() to get the current value.
  */

  /*!
    \fn double Axis::minorTicks() const
    Return the interval for ploting minor ticks.

    Use setMinorTicks() to modify it.
  */

  /*!
    \fn void Axis::setMinorTicks(double m)
    Set the interval for ploting minor ticks. For log scales, use only values less
    than 10.

    Use tick() to get the current value.
  */

  /*!
    \fn bool Axis::autoTicks() const
    Return true is auto labeling is on.

    Use setAutoTickss() to modify it.
  */

  /*!
    \fn bool Axis::zoomEnabled() const
    Return true if the zoom is enabled.

    Use setZoomEnabled() to modify it.
  */

  /*!
    \fn void Axis::setZoomEnabled(bool m)
    Enable or disable the zoom feature according to \a m value.

    Use zoomEnabled() to get the current value.
  */

  /*!
    \fn char Axis::numberType() const
    Return the type of numbers used for labels ('f', 'g' or 't').

    Use setNumberType() to modify it.
  */

  /*!
    \fn int Axis::numberPrecision() const
    Return the current precision to display numbers.

    Use setNumberPrecision() to modify it.
  */

  /*!
    \fn void Axis::setNumberPrecision(int p)
    Set the precision to display numbers. For log scales, it is the precision of the
    lowest number (minVal()). The precision is incremented for each power of ten: for
    instance 0.001, 0.01, 0.1, 1, 10, ... if numberPrecision() is 3 and minVal() is 0.001.

    Use numberPrecision() to get the current value.
  */

  /*!
    \fn ScaleType Axis::scaleType()
  */

  /*!
    \fn bool Axis::reversedScale()

    Return true if the ploting direction of the axis is reversed, see setReversedScale()
    for details
  */

  /*!
    \fn void Axis::setReversedScale(bool b)

    Select the ploting direction of the axis, if true, it means:

    \li from left to right for an horizontal axis
    \li from bottom to top for a vertical axis
  */

  /*!
    \fn void Axis::setTitleInversedScale(QString s)
  */

  /*!
    \fn QString Axis::titleInversedScale() const
  */

  /*!
    \fn void Axis::setNumberPrecisionInversedScale(int np)
  */

  /*!
    \fn int Axis::numberPrecisionInversedScale() const

  */

  /*!
    \fn Scale::Orientation Axis::orientation() const
  */

  /*!
    \fn void Axis::setOrientation(Scale::Orientation o)
  */

  /*!
    \fn QString Axis::title() const
  */

  /*!
    \fn void Axis::setTitle(QString s)
  */

  /*!
    \fn QString Axis::fontString() const
  */

  /*!
    \fn void Axis::setFontString(QString fs)
  */

  /*!
    \fn SizeType Axis::sizeType() const
  */

  /*!
    \fn void Axis::setSizeType(SizeType st)
  */

  /*!
    \fn double Axis::sizeInfo() const
  */

  /*!
    \fn void Axis::setSizeInfo(double s)
  */

  /*!
    \fn double Axis::printLineWeight() const
  */

  /*!
    \fn void Axis::setPrintLineWeight(double val)
  */

  /*!
    \fn double Axis::printTickLength() const
  */

  /*!
    \fn void Axis::setPrintTickLength(double val)
  */

  /*!
    Construct an axis generally not called directly, use either HorizontalAxis or VerticalAxis
  */
  Axis::Axis(QWidget *parent)
      : QWidget(parent)
  {
    TRACE;
    setWidgetColor(this, Qt::white);
    setMouseTracking(true);

    _lastMousePosition=0;
    _mouseTrack=false;
    _stringLabels=nullptr;

    _orientation=North;
    _orientationBlocked=false;
    _showLabels=true;
    _numberType='f';
    _numberPrecision=0;
    _unitFactor=1.0;
    _autoPrecision=true;
    _numberPrecisionInversedScale=1;
    _zoomEnabled=true;
    _autoTicks=true;
    _sizeType=TotalSize;
    _sizeInfo=5;
    _lineWeight=0.1;
    _tickSize=2;
    _content=nullptr;

    _propertiesAction=new QAction(tr("&Properties"), this);
    _propertiesAction->setObjectName("Properties");
    _propertiesAction->setStatusTip(tr("Set properties of object"));
    _propertiesAction->setShortcut(tr("Ctrl+Alt+P"));
    _propertiesAction->setShortcutContext(Qt::WidgetShortcut);
    connect(_propertiesAction, SIGNAL(triggered(bool)), this, SLOT(showProperties()));
  }

  Axis::~Axis()
  {
    TRACE;
    delete _stringLabels;
  }

  void Axis::operator=(const Axis& o)
  {
    TRACE;
    setOrientation(o._orientation);
    scale()=o.constScale();
    _showLabels=o._showLabels;
    _numberType=o._numberType;
    _numberPrecision=o._numberPrecision;
    _unitFactor=o._unitFactor;
    _autoPrecision=o._autoPrecision;
    _numberPrecisionInversedScale=o._numberPrecisionInversedScale;
    _zoomEnabled=o._zoomEnabled;
    _autoTicks=o._autoTicks;
    _title=o._title;
    _titleInversedScale=o._titleInversedScale;
    _sizeType=o._sizeType;
    _sizeInfo=o._sizeInfo;
    _lineWeight=o._lineWeight;
    _tickSize=o._tickSize;
    setFont(o.font());
    emit rangeChanged();
    if(_autoPrecision) {
      calculatePrecicion();
    }
    emit sizeChanged();
  }

  Rect Axis::boundingRect() const
  {
    TRACE;
    return _content->boundingRect();
  }

  void Axis::setScaleType(Scale::Type st)
  {
    TRACE;
    if(st!=scaleType()) {
      Scale& s=scale();
      s.setType(st);
      switch (s.type()) {
      case Scale::Inversed:
      case Scale::Log:
      case Scale::InversedLog:
        if(s.globalMinimum()<=1e-16) {
          s.setGlobalMinimum(1e-16);
        }
        if(s.globalMaximum()<=1e-16) {
          s.setGlobalMaximum(1e-16);
        }
        if(s.minimum()<=1e-16) {
          s.setMinimum(1e-16);
        }
        if(s.maximum()<=1e-16) {
          s.setMaximum(1e-16);
        }
        break;
      case Scale::Linear:
      case Scale::AbsoluteTime:
      case Scale::RelativeTime:
        break;
      }
      if(_autoTicks) s.autoTicks();
      s.cacheTicks();
      if(_autoPrecision) calculatePrecicion();
      setScaleTransformation(width(), height());
    }
  }

  /*!
    Set the minimum and the maximum values of the axis. The \a min and \a max are swaped
    if necessary. The scale is always updated.

    Use minimum() or maximum() to get the current values.
  */
  void Axis::setRange(double min, double max)
  {
    TRACE;
    Scale& s=scale();
    bool zoomed=isZoomed();
    s.setGlobalMinimum(min);
    s.setGlobalMaximum(max);
    setVisibleRange(zoomed);
    emit sizeChanged();
  }

  /*!
    \fn Axis::isZoomed()
    Return true if the visible range is smaller than the [min, max] range, i.e. zoomed range.
  */

  QString Axis::numberTypeString() const
  {
    switch(_numberType) {
    case 'f': break;
    case 'e': return "Scientific";
    }
    return "Fixed";
  }

  void Axis::setNumberType(QString t)
  {
    switch(t[0].unicode()) {
    case 'S':
      if(t=="Scientific") {
        setNumberType('e');
      }
      return;
    case 'F':
      setNumberType('f');
      return;
    default:
      break;
    }
    App::log(tr("Unrecognized number type %1\n").arg(t) );
  }

  /*!
    Set the minimum and the maximum visible values of the axis. The min and max are swaped
    if necessary. The scale is always updated.

    Use this function, once setMinimum() and setMaximum() have been called separately to update the
    visible range. In case of zoomed area, do not call this function to avoid current zoom rect
    to be lost. To know if the axis range has been zoomed call isZoomed();

    For instance:
    \code
    VerticalAxis axis;
    bool zoomed=axis.isZoomed();
    axis.setGlobalMinimum(25.0);
    axis.setVisibleRange(zoomed);
    \endcode
  */
  void Axis::setVisibleRange(bool zoomed)
  {
    TRACE;
    const Scale& cs=constScale();
    if(cs.globalMaximum()<cs.globalMinimum()) {
      double tmp;
      tmp=cs.globalMinimum();
      Scale& s=scale();
      s.setGlobalMinimum(cs.globalMaximum());
      s.setGlobalMaximum(tmp);
    }
    if(!zoomed) {
      setVisibleRange(cs.globalMinimum(), cs.globalMaximum());
    }
    emit rangeChanged();
  }

  void Axis::setVisibleRange(double val1, double val2)
  {
    TRACE;
    Scale& s=scale();
    if(val1<val2) {
      s.setMinimum(val1);
      s.setMaximum(val2);
    } else {
      s.setMinimum(val2);
      s.setMaximum(val1);
    }
    if(_autoTicks) {
      s.autoTicks();
    }
    s.cacheTicks();
    setScaleTransformation(width(), height());
    if(_autoPrecision) {
      calculatePrecicion();
    }
  }

  void Axis::addStringLabel(QString lab, bool updadeScale)
  {
    TRACE;
    if( !_stringLabels) {
      _stringLabels=new QVector<QString>;
    }
    _stringLabels->append(lab);
    if(updadeScale) {
      setRange(0.5, static_cast<double>(_stringLabels->count())+0.5);
    }
  }

  void Axis::removeStringLabels()
  {
    TRACE;
    if(_stringLabels) {
      delete _stringLabels;
      _stringLabels=nullptr;
    }
  }

  /*!
    Change the auto labelling to m.
    According to the min and max values, and the current scale type, typical
    rounded values are set for label() and tick().

    \sa autoTicks() and autoTicks().
  */
  void Axis::setAutoTicks(bool m)
  {
    TRACE;
    _autoTicks=m;
    if(_autoTicks) {
      scale().autoTicks();
      scale().cacheTicks();
    }
  }

  /*!
    Remove/set the lines that track the mouse position.
    Usually connected by AxisWindow to GraphContent::mouseInside(bool)
  */
  void Axis::setMouseTrack (bool t)
  {
    TRACE;
    _mouseTrack=t;
    update();
  }

  void Axis::setGraphContent(GraphContent * gc)
  {
    _content=gc;
  }

  Scale& Axis::scale()
  {
    switch(_orientation) {
    case North:
    case South:
      return _content->scaleX();
    default:
      return _content->scaleY();
    }
  }

  const Scale& Axis::constScale() const
  {
    switch(_orientation) {
    case North:
    case South:
      return _content->scaleX();
    default:
      return _content->scaleY();
    }
  }

  void Axis::setAutoPrecision(bool p)
  {
    TRACE;
    _autoPrecision=p;
    if(_autoPrecision) calculatePrecicion();
  }

  /*!
    Automatically adjust the numerical precision.

    It must be called every time, the visible min or max is changed or tick interval.
    Make sure that ticks are updated before calculating the precision if autoticks is on.
  */
  void Axis::calculatePrecicion()
  {
    TRACE;
    const Scale& s=constScale();
    double visMin, visMax, majorTicks;
    SAFE_UNINITIALIZED(visMin,0);
    SAFE_UNINITIALIZED(visMax,0);
    SAFE_UNINITIALIZED(majorTicks,0);
    switch (s.type()) {
    case Scale::Linear:
    case Scale::RelativeTime:
    case Scale::Log:
      visMin=s.minimum()*_unitFactor;
      visMax=s.maximum()*_unitFactor;
      majorTicks=s.majorTicks()*_unitFactor;
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      visMin=_unitFactor/s.maximum();
      visMax=_unitFactor/s.minimum();
      majorTicks=s.majorTicks()*_unitFactor;
      break;
    case Scale::AbsoluteTime:
      visMin=s.minimum();
      visMax=s.maximum();
      if(visMax-visMin<=60.0) {
        const QVector<double>& ticks=s.majorTickValues();
        if(ticks.count()>=2) {
          majorTicks=ticks.at(1)-ticks.at(0);
        } else {
          majorTicks=s.majorTicks();
        }
      } else {
        majorTicks=1.0;
      }
      break;
    }
    int numberPrecision=0;
    switch(s.type()) {
    case Scale::Log:
    case Scale::InversedLog:
      if(_numberType=='e') {
        numberPrecision=qCeil(round(-log10(s.majorTicks())*100.0)*0.01);
      } else {
        double baseLabel=pow(10.0, floor(log10(visMin)));
        double label=baseLabel*s.majorTicks();
        numberPrecision=qCeil(round(-log10(label)*100.0)*0.01);
      }
      break;
    case Scale::Linear:
    case Scale::Inversed:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      if(_numberType=='e') {
        double max=fabs(visMax);
        if(visMin<-max) {
          max=-visMin;
        }
        max=floor(max/majorTicks)*majorTicks;
        int baseExponent=qFloor(log10(max));
        numberPrecision=qCeil(round(-log10(majorTicks)*100.0)*0.01)+baseExponent;
      } else {
        numberPrecision=qCeil(round(-log10(majorTicks)*100.0)*0.01);
      }
      break;
    }
    if(numberPrecision<0) {
      numberPrecision=0;
    } else if(numberPrecision>20) {
      numberPrecision=20;
    }
    if(numberPrecision!=currentNumberPrecision()) {
      setCurrentNumberPrecision(numberPrecision);
      emit sizeChanged();
    }
  }

  QString Axis::stringLabels() const
  {
    TRACE;
    if(_stringLabels) {
      int n=_stringLabels->count();
      QString tmp, s;
      for(int i=0;i < n;i++ ) {
        tmp=_stringLabels->at(i);
        tmp.replace( ",", "\\," );
        if(i > 0)
          s += ",";
        s += tmp;
      }
      return s;
    }
    return QString();
  }

  void Axis::setStringLabels(QString s)
  {
    TRACE;
    removeStringLabels();
    const QChar * p=s.data();
    StringSection label(p, 0);
    while(true) {
      switch (p[ 0 ].unicode()) {
      case 0x005C:       // '\'
        p++;
        break;
      case 0x002C:       // ','
        label.setEnd(p);
        addStringLabel(label.toString());
        label.set(p + 1, 0);
        break;
      case 0x0000:
        label.setEnd(p);
        if(!label.isEmpty()) addStringLabel(label.toString());
        return ;
      default:
        break;
      }
      p++;
    }
  }

  QString Axis::scaleTypeString() const
  {
    TRACE;
    switch (scaleType()) {
    case Scale::Inversed:
      return "Inversed";
    case Scale::InversedLog:
      return "InversedLog";
    case Scale::Log:
      return "Log";
    case Scale::AbsoluteTime:
      return "AbsoluteTime";
    case Scale::RelativeTime:
      return "RelativeTime";
    default:
      return "Linear";
    }
  }

  void Axis::setScaleType(QString t)
  {
    TRACE;
    switch(t.count()) {
    case 0:
      return;
    case 1: // Compatibility
      switch(t[0].unicode()) {
      case 'l':
        setScaleType(Scale::Log);
        break;
      case 'i':
        setScaleType(Scale::Inversed);
        break;
      default:
        setScaleType(Scale::Linear);
        break;
      }
      break;
    default:
      switch(t[1].unicode()) {
      case 'o':
        if(t=="Log") {
          setScaleType(Scale::Log);
          return;
        }
        break;
      case 'n':
        if(t=="Inversed") {
          setScaleType(Scale::Inversed);
          return;
        } else if(t=="InversedLog") {
          setScaleType(Scale::InversedLog);
          return;
        }
        break;
      case 'b':
        if(t=="AbsoluteTime") {
          setScaleType(Scale::AbsoluteTime);
          return;
        }
        break;
      case 'e':
        if(t=="RelativeTime") {
          setScaleType(Scale::RelativeTime);
          return;
        }
        break;
      case 'i':
        if(t=="Linear") {
          setScaleType(Scale::Linear);
          return;
        }
        break;
      default:
        break;
      }
    }
    App::log(tr("Unknown scale type '%1'\n").arg(t));
  }

  /*!
    Compatibility function only. Never use it.
  */
  void Axis::setPositionString(QString p)
  {
    TRACE;
    switch (_orientation) {
    case North:
    case South:
      if(p=="top" )
        setOrientation(South);
      else
        setOrientation(North);
      break;
    case East:
    case West:
      if(p=="right" )
        setOrientation(West);
      else
        setOrientation(East);
      break;
    }
  }

  /*!
    \fn void Axis::setOrientationBlocked(blocked b)
    Prevent any change in the orientation from vertical to horizontal. Set vertical
    or horizontal orientation before blocking. By default axes are horizontal.

    \sa setOrientation(Orientation o).
  */

  /*!
    Set orientation of axis. If orientation is blocked (setOrientationBlocked()), it is
    not possible to change from East or West to North or South and vice-versa. This
    is mainly used by AxisWindow to block axis in their vertical or horizontal
    positions.
  */
  void Axis::setOrientation(Orientation o)
  {
    TRACE;
    if(_orientationBlocked) {
      switch (_orientation) {
      case North:
      case South:
        _orientation=o==North ? North : South;
        break;
      case East:
      case West:
        _orientation=o==East ? East : West;
        break;
      }
    } else {
      _orientation=o;
    }
    emit orientationChanged();
  }

  QString Axis::orientationString() const
  {
    TRACE;
    switch(_orientation) {
    case North:
      return "North";
    case South:
      return "South";
    case East:
      return "East";
    default:
      return "West";
    }
  }

  void Axis::setOrientationString(QString o)
  {
    TRACE;
    switch(o[0].unicode()) {
    case 'S':
      setOrientation(South);
      break;
    case 'W':
      setOrientation(West);
      break;
    case 'E':
      setOrientation(East);
      break;
    default:
      setOrientation(North);
      break;
    }
  }

  QString Axis::sizeTypeString() const
  {
    TRACE;
    switch (sizeType()) {
    case Axis::Scaled:
      return "Scaled";
    case Axis::AxisSize:
      return "AxisSize";
    case Axis::TotalSize:
      return "TotalSize";
    }
    return "TotalSize";
  }

  void Axis::setSizeType(QString st)
  {
    TRACE;
    switch (st[0].unicode()) {
    case 'S':
      setSizeType(Axis::Scaled);
      break;
    case 'A':
      setSizeType(Axis::AxisSize);
      break;
    default:
      setSizeType(Axis::TotalSize);
      break;
    }
  }

  int Axis::currentNumberPrecision() const
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      return numberPrecisionInversedScale();
    }
    return numberPrecision();
  }

  void Axis::setCurrentNumberPrecision(int p)
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      setNumberPrecision(p);
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      setNumberPrecisionInversedScale(p);
      break;
    }
  }

  int Axis::currentInversedNumberPrecision() const
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      return numberPrecision();
    }
    return numberPrecisionInversedScale();
  }

  void Axis::setCurrentInversedNumberPrecision(int p)
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      setNumberPrecisionInversedScale(p);
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      setNumberPrecision(p);
      break;
    }
  }

  QString Axis::currentTitle() const
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      return titleInversedScale();
    }
    return title();
  }

  void Axis::setCurrentTitle(QString s)
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      setTitle(s);
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      setTitleInversedScale(s);
      break;
    }
  }

  QString Axis::currentInversedTitle() const
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      return title();
    }
    return titleInversedScale();
  }

  void Axis::setCurrentInversedTitle(QString s)
  {
    TRACE;
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      setTitleInversedScale(s);
      break;
    case Scale::Inversed:
    case Scale::InversedLog:
      setTitle(s);
      break;
    }
  }

  void Axis::setCurrentLine(int line)
  {
    TRACE;
    Scale& scl=scale();
    switch (_orientation) {
    case North:
    case South:
      scl.setHorizontalCurrentLine(line);
      break;
    case East:
    case West:
      scl.setVerticalCurrentLine(line);
      break;
    }
    if(_autoPrecision) calculatePrecicion();
    setScaleTransformation(width(), height());
    scl.cacheTicks();
  }

  int Axis::currentLine() const
  {
    const Scale& scl=constScale();
    switch (_orientation) {
    case North:
    case South:
      return scl.horizontalCurrentLine();
    case East:
    case West:
      return scl.verticalCurrentLine();
    }
    return 0;
  }

  void Axis::setScaleTransformation(int w, int h)
  {
    Scale& scl=scale();
    scl.checkLimits();
    switch (_orientation) {
    case North:
    case South:
      scl.setHorizontalTransformation(w);
      break;
    case East:
    case West:
      scl.setVerticalTransformation(h);
      break;
    }
  }

  void Axis::trackMouse (QPoint mouseP)
  {
    TRACE;
    switch (_orientation) {
    case North:
    case South:
      _lastMousePosition=mouseP.x();
      break;
    case East:
    case West:
      _lastMousePosition=mouseP.y();
      break;
    }
    update();
  }

  void Axis::resizeEvent (QResizeEvent * )
  {
    TRACE;
    setScaleTransformation(width(), height());
  }

  void Axis::paintEvent(QPaintEvent * )
  {
    TRACE;
    QPainter p(this);
    p.fillRect(0, 0, width(), height(), Qt::white);
    switch (_orientation) {
    case North:
    case South:
      paintHorizontal(p, SciFigsGlobal::screenResolution(), width(), height(), false);
      if(isMouseTrack()) {
        // draw a line on last mouse position
        p.setPen(QPen( Qt::blue, 1, Qt::DotLine) );
        p.drawLine(_lastMousePosition, 0 , _lastMousePosition, height());
      }
      break;
    case East:
    case West:
      paintVertical(p, SciFigsGlobal::screenResolution(), width(), height(), false);
      if(isMouseTrack()) {
        // draw a line on last mouse position
        p.setPen(QPen( Qt::blue, 1, Qt::DotLine) );
        p.drawLine(0, _lastMousePosition , width(), _lastMousePosition);
      }
      break;
    }
  }

  void Axis::paint(QPainter& p, double dotpercm, int w, int h, bool mask)
  {
    TRACE;
    switch (_orientation) {
    case North:
    case South:
      if(h) paintHorizontal(p, dotpercm, w, h, mask);
      break;
    case East:
    case West:
      if(w) paintVertical(p, dotpercm, w, h, mask);
      break;
    }
  }

  /*!
    Set the scale to print
  */
  void Axis::setPrintScale(int w, int h)
  {
    TRACE;
    Scale& scl=scale();
    scl.setMinimum(scl.globalMinimum());
    scl.setMaximum(scl.globalMaximum());
    if(_autoTicks) scl.autoTicks();
    setScaleTransformation(w, h);
  }

  inline void Axis::setPenFont(QPainter& p, bool mask, double dotpercm) const
  {
    TRACE;
    if(mask) {
      p.setPen(QPen(Qt::color1, qRound(_lineWeight*dotpercm*0.1), Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
    } else {
      p.setPen(QPen(Qt::black, qRound(_lineWeight*dotpercm*0.1), Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
    }
    _font.setFont(p, dotpercm);
  }

  QString Axis::label(int index, int numberPrecision, double unitFactor) const
  {
    const Scale& scl=constScale();
    double val=scl.majorTickValues().at(index);
    if(_showLabels) {
      switch(scl.type()) {
      case Scale::Linear:
        if(_stringLabels && (val>0.0 && val<=_stringLabels->count()) && round(val)==val) {
           return (*_stringLabels)[qRound(val)-1];
        } else {
          return Number::toString(val*unitFactor, _numberType, numberPrecision);
        }
      case Scale::Log:
        if(_numberType=='e') {
          return Number::toString(val*unitFactor, _numberType, numberPrecision);
        } else {
          numberPrecision+=scl.majorTickPrecision().at(index);
          if(numberPrecision<0) {
            numberPrecision=0;
          }
          return Number::toString(val*unitFactor, _numberType, numberPrecision);
        }
      case Scale::Inversed:
        return Number::toString(val*unitFactor, _numberType, numberPrecision);
      case Scale::InversedLog:
        if(_numberType=='e') {
          return Number::toString(val*unitFactor, _numberType, numberPrecision);
        } else {
          numberPrecision+=scl.majorTickPrecision().at(index);
          if(numberPrecision<0) {
            numberPrecision=0;
          }
          return Number::toString(val*unitFactor, _numberType, numberPrecision);
        }
      case Scale::RelativeTime:
        return Number::secondsToDuration(val*unitFactor, numberPrecision);
      case Scale::AbsoluteTime:
        DateTime t(scl.timeReference());
        t.addSeconds(val);
        return t.toString(scl.timeFormat(), numberPrecision);
      }
    }
    return QString();
  }

  void Axis::paintHorizontal(QPainter& p, double dotpercm, int w, int h, bool mask)
  {
    TRACE;
    const Scale& scl=constScale();

    setPenFont(p, mask, dotpercm);
    double dotpermm=dotpercm*0.1;
    double tmp=_tickSize*dotpermm;
    int tickLength=qRound(tmp);
    int tickShift=qRound(tmp+_lineWeight*dotpermm);

    int numberPrecision=currentNumberPrecision();

    int posMainLine, posMajorTicks, posLabels, posMinorTicks;
    if(_orientation==North) {
      posMainLine=0;
      posMajorTicks=tickLength;
      posLabels=tickShift;
      posMinorTicks=tickLength/2;
    } else {
      posMainLine=h;
      posMajorTicks=h-tickLength;
      posLabels=h-tickShift;
      posMinorTicks=h-tickLength/2;
    }

    int x=0;
    QString str;
    QRect r;
    // draw labels and primary ticks
    for(int i=scl.majorTickValues().count()-1; i>=0; i--) {
      QString str=label(i, numberPrecision, _unitFactor);
      x=scl.a2s(scl.majorTickValues().at(i));
      p.drawLine(x, posMainLine, x, posMajorTicks);
      if(_showLabels) {
        r=p.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignTop, str);
        if(_orientation==North) {
          p.drawText(x-r.width()/2, posLabels+(r.height()*2)/3, str);
        } else {
          p.drawText(x-r.width()/2, posLabels, str);
        }
      }
    }

    // draw secondary ticks
    for(int i=scl.minorTickValues().count()-1; i>=0; i--) {
      x=scl.a2s(scl.minorTickValues().at(i));
      p.drawLine(x, posMainLine, x, posMinorTicks);
    }

    // draw title
    str=currentTitle();
    if(str.length() > 0) {
      r=p.boundingRect(0, 0, w, INT_MAX, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, str);
      if(_orientation==North) {
        p.drawText(0, h-r.height(), w, r.height(),
                   Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, str);
      } else {
        p.drawText(0, 0, w, r.height(), Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, str);
      }
    }
  }

  void Axis::paintVertical(QPainter& p, double dotpercm, int w, int h, bool mask)
  {
    TRACE;
    const Scale& scl=constScale();

    setPenFont(p, mask, dotpercm);
    double dotpermm=dotpercm*0.1;
    double tmp=_tickSize*dotpermm;
    int tickLength=qRound(tmp);
    int tickShift=qRound(tmp+_lineWeight*dotpermm);

    int numberPrecision=currentNumberPrecision();

    int posMainLine, posMajorTicks, posLabel, posMinorTicks, posRotation, posXTitle, posYTitle;
    if(_orientation==East) {
      posMainLine=w;
      posMajorTicks=w-tickLength;
      posLabel=w - tickShift;
      posMinorTicks=w-tickLength/2;
      posRotation=-90;
      posXTitle=-h;
      posYTitle=0;
    } else {
      posMainLine=0;
      posMajorTicks=tickLength;
      posLabel=tickShift;
      posMinorTicks=tickLength/2;
      posRotation=90;
      posXTitle=0;
      posYTitle=-w;
    }

    int y=0;
    QString str;
    QRect r;
    // draw labels and major ticks
    for(int i=scl.majorTickValues().count()-1; i>=0; i--) {
      str=label(i, numberPrecision, _unitFactor);
      y=scl.a2s(scl.majorTickValues().at(i));
      p.drawLine(posMainLine, y, posMajorTicks, y);
      if(_showLabels) {
        r=p.boundingRect(0, 0, INT_MAX, INT_MAX, Qt::AlignLeft | Qt::AlignTop, str);
        if(_orientation==East) {
          p.drawText(posLabel-r.width(), y+r.height()/3, str);
        } else {
          p.drawText(posLabel, y+r.height()/3, str);
        }
      }
    }

    // draw secondary ticks
    for(int i=scl.minorTickValues().count()-1; i>=0; i--) {
      y=scl.a2s(scl.minorTickValues().at(i));
      p.drawLine(posMainLine, y, posMinorTicks, y);
    }

    // draw title
    str=currentTitle();
    if(str.length() > 0) {
      r=p.boundingRect(0, 0, h, INT_MAX, Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, str);
      p.save();
      p.rotate(posRotation);
      p.drawText(posXTitle, posYTitle, h, r.height(),
                  Qt::AlignHCenter | Qt::AlignTop | Qt::TextWordWrap, str);
      p.restore();
    }
  }

  QRectF Axis::textBoundingRect(QPainter * p, const QString& str, int length, int flags) const
  {
    TRACE;
    if(p) {
      return p->boundingRect(QRectF(0.0, 0.0, length, INT_MAX), flags, str);
    } else {
      return _font.boundingRect(str, length, flags);
    }
  }

  /*!
    Compute thickness of axis from a fixed length
  */
  int Axis::printThickness(int length, double dotpercm, QPainter * p)
  {
    TRACE;
    QString str;
    QRectF r;
    if(p) {
      _font.setFont(*p, dotpercm);
    }
    double maxThickness=0.0;
    int numberPrecision=currentNumberPrecision();
    switch (_orientation) {
    case North:
    case South:
      if(_showLabels) {
        if(_stringLabels) {
          for(int i=_stringLabels->count()-1; i>=0; i--) {
            r=textBoundingRect(p, (*_stringLabels)[i]);
            if(r.height()>maxThickness) {
              maxThickness=r.height();
            }
          }
        } else {
          if(constScale().isMajorTicksEmpty()) {
            maxThickness=0.0;
          } else {
            r=textBoundingRect(p, label(0, numberPrecision, _unitFactor));
            maxThickness=r.height();
            r=textBoundingRect(p, label(constScale().majorTickValues().count()-1, numberPrecision, _unitFactor));
            if(r.height()>maxThickness) {
              maxThickness=r.height();
            }
          }
        }
      }
      maxThickness+=(_tickSize+_lineWeight)*dotpercm*0.1;
      // title
      str=currentTitle();
      if(!str.isEmpty()) {
        r=textBoundingRect(p, str, length, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap);
        maxThickness+=r.height();
      }
      break;
    case East:
    case West:
      if(_showLabels) {
        if(_stringLabels) {
          for(int i=_stringLabels->count()-1; i>=0; i--) {
            r=textBoundingRect(p, (*_stringLabels)[i]);
            if(r.width()>maxThickness) {
              maxThickness=r.width();
            }
          }
        } else {
          if(constScale().isMajorTicksEmpty()) {
            maxThickness=0.0;
          } else {
            r=textBoundingRect(p, label(0, numberPrecision, _unitFactor));
            maxThickness=r.width();
            r=textBoundingRect(p, label(constScale().majorTickValues().count()-1, numberPrecision, _unitFactor));
            if(r.width()>maxThickness) {
              maxThickness=r.width();
            }
          }
        }
      }
      maxThickness+=(_tickSize+_lineWeight)*dotpercm*0.1;
      // title
      str=currentTitle();
      if(!str.isEmpty()) {
        r=textBoundingRect(p, str, length, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap);
        maxThickness+=r.height();
      }
      break;
    }
    return qRound(maxThickness);
  }

  uint Axis::_tabScale=PropertyProxy::uniqueId();
  uint Axis::_tabFormat=PropertyProxy::uniqueId();

  /*!

  */
  void Axis::addProperties(PropertyProxy * pp)
  {
    TRACE;
    if(pp->setCurrentTab(_tabScale)) {
      pp->addReference(this);
      AxisScaleProperties * w=static_cast<AxisScaleProperties *>(pp->currentTabWidget());
      w->setCurrentAxis(this);
    } else {
      AxisScaleProperties * w=new AxisScaleProperties;
      pp->addTab(_tabScale, tr("Scale"), w, this);
      w->setCurrentAxis(this);
    }
    if(pp->setCurrentTab(_tabFormat)) {
      pp->addReference(this);
    } else {
      AxisFormatProperties * w=new AxisFormatProperties;
      pp->addTab(_tabFormat, tr("Format"), w, this);
      if(pp->setCurrentTab(_tabScale)) {
        w->linkTo(static_cast<AxisScaleProperties *>(pp->currentTabWidget()));
      }
    }
  }

  /*!
  */
  void Axis::removeProperties(PropertyProxy * pp)
  {
    TRACE;
    pp->removeTab(_tabScale, this);
    pp->removeTab(_tabFormat, this);
  }

  void Axis::properties(PropertyWidget * w) const
  {
    TRACE;
    if(w->id()==_tabScale) {
      double min, max;
      int prec;
      SAFE_UNINITIALIZED(min, 0.0);
      SAFE_UNINITIALIZED(max, 0.0);
      SAFE_UNINITIALIZED(prec, 0);
      switch(scaleType()) {
      case Scale::Linear:
      case Scale::Log:
      case Scale::AbsoluteTime:
      case Scale::RelativeTime:
        min=minimum()*_unitFactor;
        max=maximum()*_unitFactor;
        prec=numberPrecision();
        break;
      case Scale::Inversed:
      case Scale::InversedLog:
        min=_unitFactor/maximum();
        max=_unitFactor/minimum();
        prec=numberPrecisionInversedScale();
        break;
      }
      prec+=2;
      if(scaleType()==Scale::AbsoluteTime) {
        w->setValue(AxisScaleProperties::MinimumValue, time(min).toString(DateTime::defaultUserFormat, prec));
        w->setValue(AxisScaleProperties::MaximumValue, time(max).toString(DateTime::defaultUserFormat, prec));
      } else {
        w->setValue(AxisScaleProperties::MinimumValue, QLocale().toString(min, _numberType, prec));
        w->setValue(AxisScaleProperties::MaximumValue, QLocale().toString(max, _numberType, prec));
      }
      w->setValue(AxisScaleProperties::MajorTicks, majorTicks());
      w->setValue(AxisScaleProperties::MinorTicks, minorTicks());
      w->setValue(AxisScaleProperties::AutoTicks, autoTicks());
      w->setValue(AxisScaleProperties::ShowLabels, showLabels());
      w->setValue(AxisScaleProperties::ZoomEnabled, zoomEnabled());
      w->setValue(AxisScaleProperties::ScaleType, AxisScaleProperties::scaleType2item(scaleType()));
      w->setValue(AxisScaleProperties::SizeType, AxisScaleProperties::sizeType2item(sizeType()));
      w->setValue(AxisScaleProperties::SizeInfo, sizeInfo());
      w->setValue(AxisScaleProperties::ReversedScale, reversedScale());
    } else if(w->id()==_tabFormat) {
      w->setValue(AxisFormatProperties::Title, currentTitle());
      w->setValue(AxisFormatProperties::Font, _font.screenFont().toString());
      w->setValue(AxisFormatProperties::Orientation, AxisFormatProperties::orientation2item(orientation()) );
      w->setValue(AxisFormatProperties::LineWeight, lineWeight());
      w->setValue(AxisFormatProperties::TickSize, tickSize());
      w->setValue(AxisFormatProperties::NumberType, AxisFormatProperties::numberType2item(numberType()));
      w->setValue(AxisFormatProperties::NumberPrecision, currentNumberPrecision());
      w->setValue(AxisFormatProperties::Autoprecision, autoPrecision());
      w->setValue(AxisFormatProperties::UnitFactor, Number::toString(unitFactor(), 'g', 6));
    }
  }

  double Axis::toDouble(const QString& text, bool& ok) const
  {
    switch(scaleType()) {
    case Scale::Linear:
    case Scale::Log:
    case Scale::InversedLog:
    case Scale::Inversed:
      break;
    case Scale::RelativeTime:
      return Number::durationToSeconds(text, ok);
    case Scale::AbsoluteTime: {
        double val=text.toDouble(&ok);  // Number of seconds from time reference or time specification are allowed
        if(ok) {
          return val;
        }
        const DateTime& tRef=timeReference();
        DateTime t;
        ok=t.fromString(text, DateTime::defaultUserFormat);
        return tRef.secondsTo(t);
      }
    }
    return Number::toDouble(text, ok);
  }

  void Axis::setProperty(uint wid, int pid, QVariant val)
  {
    TRACE;
    bool visibleRangeChange=false;
    bool previouslyZoomed=isZoomed();
    bool deepUpdateChange=false;
    bool ok=true;
    double dVal;

    if(wid==_tabScale) {
      switch(pid) {
      case AxisScaleProperties::MinimumValue:
        dVal=toDouble(val.toString(), ok);
        if(ok) {
          switch(scaleType()) {
          case Scale::Linear:
          case Scale::Log:
          case Scale::AbsoluteTime:
          case Scale::RelativeTime:
            setMinimum(dVal/_unitFactor);
            break;
          case Scale::Inversed:
          case Scale::InversedLog:
            setMaximum(1.0/(dVal*_unitFactor));
            break;
          }
          visibleRangeChange=true;
        }
        break;
      case AxisScaleProperties::MaximumValue:
        dVal=toDouble(val.toString(), ok);
        if(ok) {
          switch(scaleType()) {
          case Scale::Linear:
          case Scale::Log:
          case Scale::AbsoluteTime:
          case Scale::RelativeTime:
            setMaximum(dVal/_unitFactor);
            break;
          case Scale::Inversed:
          case Scale::InversedLog:
            setMinimum(1.0/(dVal*_unitFactor));
            break;
          }
          visibleRangeChange=true;
        }
        break;
      case AxisScaleProperties::MajorTicks:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          if(autoTicks()) {
            setAutoTicks(false);
          }
          setMajorTicks(dVal);
          scale().cacheTicks();
          deepUpdateChange=true;
          if(autoPrecision()) {
            calculatePrecicion();
          }
        }
        break;
      case AxisScaleProperties::MinorTicks:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          if(autoTicks()) {
            setAutoTicks(false);
          }
          setMinorTicks(dVal);
          scale().cacheTicks();
          deepUpdateChange=true;
          if(autoPrecision()) {
            calculatePrecicion();
          }
        }
        break;
      case AxisScaleProperties::AutoTicks:
        setAutoTicks(val.toBool());
        if(autoPrecision()) {
          calculatePrecicion();
        }
        deepUpdateChange=true;
        break;
      case AxisScaleProperties::ShowLabels:
        setShowLabels(val.toBool());
        break;
      case AxisScaleProperties::ZoomEnabled:
        setZoomEnabled(val.toBool());
        break;
      case AxisScaleProperties::ScaleType:
        setScaleType(AxisScaleProperties::item2scaleType(val.toInt()));
        visibleRangeChange=true;
        break;
      case AxisScaleProperties::ReversedScale:
        setReversedScale(val.toBool());
        visibleRangeChange=true;
        break;
      case AxisScaleProperties::SizeType:
        if(sizeType()!=Axis::Scaled) {
          setSizeType(AxisScaleProperties::item2sizeType(val.toInt()));
          if(sizeType()==Axis::Scaled) {
            setSizeInfo(( maximum()-minimum())/sizeInfo() * 100.0);
          }
        } else {
          setSizeType(AxisScaleProperties::item2sizeType(val.toInt()));
          if(sizeType()!=Axis::Scaled) {
            setSizeInfo(( maximum()-minimum())/sizeInfo() * 100.0);
          }
        }
        deepUpdateChange=true;
        break;
      case AxisScaleProperties::SizeInfo:
        setSizeInfo(val.toDouble());
        break;
      default:
        break;
      }
    } else if(wid==_tabFormat) {
      switch(pid) {
      case AxisFormatProperties::Title:
        setCurrentTitle(val.toString());
        break;
      case AxisFormatProperties::Font: {
          QFont f;
          if(f.fromString(val.toString()) ) {
            _font=f;
          }
        }
        break;
      case AxisFormatProperties::Orientation:
        setOrientation(AxisFormatProperties::item2orientation(val.toInt()) );
        break;
      case AxisFormatProperties::LineWeight:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          setLineWeight(dVal);
        }
        break;
      case AxisFormatProperties::TickSize:
        dVal=Number::toDouble(val, ok);
        if(ok) {
          setTickSize(dVal);
        }
        break;
      case AxisFormatProperties::NumberType:
        setNumberType(AxisFormatProperties::item2numberType(val.toInt()) );
        if(autoPrecision()) visibleRangeChange=true;
        break;
      case AxisFormatProperties::NumberPrecision:
        setCurrentNumberPrecision(val.toInt());
        break;
      case AxisFormatProperties::Autoprecision:
        setAutoPrecision(val.toBool());
        visibleRangeChange=true;
        break;
      case AxisFormatProperties::UnitFactor:
        dVal=Number::toDouble(val, ok);
        if(ok && dVal>0.0) {
          setUnitFactor(dVal);
          if(autoPrecision()) {
            visibleRangeChange=true;
          }
        }
        break;
      default:
        break;
      }
    }
    if(visibleRangeChange) {
      setTimeReference(timeReference());
      bool tmp=zoomEnabled();
      setZoomEnabled(true);
      setVisibleRange(previouslyZoomed);
      setZoomEnabled(tmp);
      deepUpdateChange=true;
    } else {
      update();
    }
    if(deepUpdateChange) {
      emit contentChanged();
    }
    emit sizeChanged();
  }

  void Axis::setTimeReference(const QString& t)
  {
    DateTime dt;
    if(t.startsWith('#') || dt.fromString(t)) {
      setTimeReference(dt);
    }
  }

  void Axis::mouseDoubleClickEvent(QMouseEvent * e)
  {
    TRACE;
    if(e->buttons() & Qt::LeftButton) {
      AxisWindow * g=_content->graph();
      g->setActive(e->modifiers());
      showProperties();
    } else {
      e->ignore();
    }
  }

  void Axis::mousePressEvent(QMouseEvent * e)
  {
    TRACE;
    if(e->buttons() & Qt::RightButton) {
      AxisWindow * w=_content->graph();
      GraphicObject * g=w->group();
      g->setActive(e->modifiers());
      QMenu m;
      QList<QAction *> l=g->actions();
      l.removeFirst();
      l.prepend(_propertiesAction);
      m.addActions(l);
      m.exec(mapToGlobal(e->pos()));
    } else {
      e->ignore();
    }
  }

  void Axis::showProperties()
  {
    TRACE;
    AxisWindow * g=_content->graph();
    switch(_orientation) {
    case North:
    case South:
      g->showProperties(AxisWindow::categoryXAxis);
      break;
    default:
      g->showProperties(AxisWindow::categoryYAxis);
      break;
    }
  }

  void Axis::setFrequency()
  {
    TRACE;
    setTitle(tr("Frequency (Hz)"));
    setTitleInversedScale(tr("Period (s)"));
    setScaleType(Scale::Log);
    setAutoTicks(false);
    setMajorTicks(5);
    setMinorTicks(1);
  }

} // namespace SciFigs
