# -*- coding: utf-8 -*-
#
# Licensed under the terms of the Qwt License
# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
# (see LICENSE file for more details)

"""
QwtScaleEngine
--------------

.. autoclass:: QwtScaleEngine
   :members:

QwtLinearScaleEngine
--------------------

.. autoclass:: QwtLinearScaleEngine
   :members:

QwtLogScaleEngine
-----------------

.. autoclass:: QwtLogScaleEngine
   :members:
"""

from __future__ import division

from qwt.interval import QwtInterval
from qwt.scale_div import QwtScaleDiv
from qwt.transform import QwtLogTransform
from qwt.math import qwtFuzzyCompare
from qwt.transform import QwtTransform

from qwt.qt.QtCore import qFuzzyCompare

import numpy as np

DBL_MAX = np.finfo(float).max
LOG_MIN = 1.0e-100
LOG_MAX = 1.0e100


def qwtLog(base, value):
    return np.log(value)/np.log(base)

def qwtLogInterval(base, interval):
    return QwtInterval(qwtLog(base, interval.minValue()),
                       qwtLog(base, interval.maxValue()))

def qwtPowInterval(base, interval):
    return QwtInterval(np.power(base, interval.minValue()),
                       np.power(base, interval.maxValue()))

def qwtStepSize(intervalSize, maxSteps, base):
    """this version often doesn't find the best ticks: f.e for 15: 5, 10"""
    minStep = divideInterval(intervalSize, maxSteps, base)
    if minStep != 0.:
        #  # ticks per interval
        numTicks = np.ceil(abs(intervalSize/minStep))-1
        #  Do the minor steps fit into the interval?
        if qwtFuzzyCompare((numTicks+1)*abs(minStep),
                           abs(intervalSize), intervalSize) > 0:
            #  The minor steps doesn't fit into the interval
            return .5*intervalSize
    return minStep

EPS = 1.0e-6

def ceilEps(value, intervalSize):
    """
    Ceil a value, relative to an interval

    :param float value: Value to be ceiled
    :param float intervalSize: Interval size
    :return: Rounded value
    
    .. seealso::
    
        :py:func:`qwt.scale_engine.floorEps()`
    """
    eps = EPS*intervalSize
    value = (value-eps)/intervalSize
    return np.ceil(value)*intervalSize

def floorEps(value, intervalSize):
    """
    Floor a value, relative to an interval

    :param float value: Value to be floored
    :param float intervalSize: Interval size
    :return: Rounded value
    
    .. seealso::
    
        :py:func:`qwt.scale_engine.ceilEps()`
    """
    eps = EPS*intervalSize
    value = (value+eps)/intervalSize
    return np.floor(value)*intervalSize

def divideEps(intervalSize, numSteps):
    """
    Divide an interval into steps
    
    `stepSize = (intervalSize - intervalSize * 10**-6) / numSteps`

    :param float intervalSize: Interval size
    :param float numSteps: Number of steps
    :return: Step size
    """
    if numSteps == 0. or intervalSize == 0.:
        return 0.
    return (intervalSize-(EPS*intervalSize))/numSteps

def divideInterval(intervalSize, numSteps, base):
    """
    Calculate a step size for a given interval
    
    :param float intervalSize: Interval size
    :param float numSteps: Number of steps
    :param int base: Base for the division (usually 10)
    :return: Calculated step size
    """
    if numSteps <= 0:
        return 0.
    v = divideEps(intervalSize, numSteps)
    if v == 0.:
        return 0.

    lx = qwtLog(base, abs(v))
    p = np.floor(lx)
    fraction = np.power(base, lx-p)
    n = base
    while n > 1 and fraction <= n/2:
        n /= 2
    
    stepSize = n*np.power(base, p)
    if v < 0:
        stepSize = -stepSize
    
    return stepSize


class QwtScaleEngine_PrivateData(object):
    def __init__(self):
        self.attributes = QwtScaleEngine.NoAttribute
        self.lowerMargin = 0.
        self.upperMargin = 0.
        self.referenceValue = 0.
        self.base = 10
        self.transform = None  # QwtTransform


class QwtScaleEngine(object):
    """
    Base class for scale engines.

    A scale engine tries to find "reasonable" ranges and step sizes
    for scales.

    The layout of the scale can be varied with `setAttribute()`.

    `PythonQwt` offers implementations for logarithmic and linear scales. 
    
    Layout attributes:
    
      * `QwtScaleEngine.NoAttribute`: No attributes
      * `QwtScaleEngine.IncludeReference`: Build a scale which includes the 
        `reference()` value
      * `QwtScaleEngine.Symmetric`: Build a scale which is symmetric to the 
        `reference()` value
      * `QwtScaleEngine.Floating`: The endpoints of the scale are supposed to 
        be equal the outmost included values plus the specified margins (see 
        `setMargins()`). If this attribute is *not* set, the endpoints of the 
        scale will be integer multiples of the step size.
      * `QwtScaleEngine.Inverted`: Turn the scale upside down
    """
    
    # enum Attribute
    NoAttribute = 0x00
    IncludeReference = 0x01
    Symmetric = 0x02
    Floating = 0x04
    Inverted = 0x08
    
    def __init__(self, base=10):
        self.__data = QwtScaleEngine_PrivateData()
        self.setBase(base)
    
    def autoScale(self, maxNumSteps, x1, x2, stepSize):
        """
        Align and divide an interval

        :param int maxNumSteps: Max. number of steps
        :param float x1: First limit of the interval (In/Out)
        :param float x2: Second limit of the interval (In/Out)
        :param float stepSize: Step size
        :return: tuple (x1, x2, stepSize)
        """
        pass
    
    def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.):
        """
        Calculate a scale division

        :param float x1: First interval limit
        :param float x2: Second interval limit
        :param int maxMajorSteps: Maximum for the number of major steps
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
        :return: Calculated scale division
        """
        pass
        
    def setTransformation(self, transform):
        """
        Assign a transformation

        :param qwt.transform.QwtTransform transform: Transformation

        The transformation object is used as factory for clones
        that are returned by `transformation()`

        The scale engine takes ownership of the transformation.

        .. seealso::
        
            :py:meth:`QwtTransform.copy()`, :py:meth:`transformation()`
        """
        assert transform is None or isinstance(transform, QwtTransform)
        if transform != self.__data.transform:
            self.__data.transform = transform
    
    def transformation(self):
        """
        Create and return a clone of the transformation 
        of the engine. When the engine has no special transformation
        None is returned, indicating no transformation.

        :return: A clone of the transfomation
        
        .. seealso::
            
            :py:meth:`setTransformation()`
        """
        if self.__data.transform:
            return self.__data.transform.copy()
    
    def lowerMargin(self):
        """
        :return: the margin at the lower end of the scale
        
        The default margin is 0.
        
        .. seealso::
        
            :py:meth:`setMargins()`
        """
        return self.__data.lowerMargin
    
    def upperMargin(self):
        """
        :return: the margin at the upper end of the scale
        
        The default margin is 0.
        
        .. seealso::
        
            :py:meth:`setMargins()`
        """
        return self.__data.upperMargin
    
    def setMargins(self, lower, upper):
        """
        Specify margins at the scale's endpoints

        :param float lower: minimum distance between the scale's lower boundary and the smallest enclosed value
        :param float upper: minimum distance between the scale's upper boundary and the greatest enclosed value
        :return: A clone of the transfomation
        
        Margins can be used to leave a minimum amount of space between
        the enclosed intervals and the boundaries of the scale.
        
        .. warning::
        
            `QwtLogScaleEngine` measures the margins in decades.
        
        .. seealso::
            
            :py:meth:`upperMargin()`, :py:meth:`lowerMargin()`
        """
        self.__data.lowerMargin = max([lower, 0.])
        self.__data.upperMargin = max([upper, 0.])
    
    def divideInterval(self, intervalSize, numSteps):
        """
        Calculate a step size for a given interval
        
        :param float intervalSize: Interval size
        :param float numSteps: Number of steps
        :return: Step size
        """
        return divideInterval(intervalSize, numSteps, self.__data.base)
    
    def contains(self, interval, value):
        """
        Check if an interval "contains" a value
        
        :param float intervalSize: Interval size
        :param float value: Value
        :return: True, when the value is inside the interval
        """
        if not interval.isValid():
            return False
        elif qwtFuzzyCompare(value, interval.minValue(), interval.width()) < 0:
            return False
        elif qwtFuzzyCompare(value, interval.maxValue(), interval.width()) > 0:
            return False
        else:
            return True
    
    def strip(self, ticks, interval):
        """
        Remove ticks from a list, that are not inside an interval
        
        :param list ticks: Tick list
        :param qwt.interval.QwtInterval interval: Interval
        :return: Stripped tick list
        """
        if not interval.isValid() or not ticks:
            return []
        if self.contains(interval, ticks[0]) and\
           self.contains(interval, ticks[-1]):
            return ticks
        return [tick for tick in ticks
                if self.contains(interval, tick)]
    
    def buildInterval(self, value):
        """
        Build an interval around a value

        In case of v == 0.0 the interval is [-0.5, 0.5],
        otherwide it is [0.5 * v, 1.5 * v]
        
        :param float value: Initial value
        :return: Calculated interval
        """
        if value == 0.:
            delta = .5
        else:
            delta = abs(.5*value)
        if DBL_MAX-delta < value:
            return QwtInterval(DBL_MAX-delta, DBL_MAX)
        if -DBL_MAX+delta > value:
            return QwtInterval(-DBL_MAX, -DBL_MAX+delta)
        return QwtInterval(value-delta, value+delta)
    
    def setAttribute(self, attribute, on=True):
        """
        Change a scale attribute
        
        :param int attribute: Attribute to change
        :param bool on: On/Off
        :return: Calculated interval
        
        .. seealso::
            
            :py:meth:`testAttribute()`
        """
        if on:
            self.__data.attributes |= attribute
        else:
            self.__data.attributes &= ~attribute
    
    def testAttribute(self, attribute):
        """
        :param int attribute: Attribute to be tested
        :return: True, if attribute is enabled
        
        .. seealso::
            
            :py:meth:`setAttribute()`
        """
        return self.__data.attributes & attribute
    
    def setAttributes(self, attributes):
        """
        Change the scale attribute
        
        :param attributes: Set scale attributes
        
        .. seealso::
            
            :py:meth:`attributes()`
        """
        self.__data.attributes = attributes
    
    def attributes(self):
        """
        :return: Scale attributes
        
        .. seealso::
            
            :py:meth:`setAttributes()`, :py:meth:`testAttribute()`
        """
        return self.__data.attributes
    
    def setReference(self, r):
        """
        Specify a reference point
        
        :param float r: new reference value
        
        The reference point is needed if options `IncludeReference` or
        `Symmetric` are active. Its default value is 0.0.
        """
        self.__data.referenceValue = r
    
    def reference(self):
        """
        :return: the reference value
        
        .. seealso::
            
            :py:meth:`setReference()`, :py:meth:`setAttribute()`
        """
        return self.__data.referenceValue
    
    def setBase(self, base):
        """
        Set the base of the scale engine

        While a base of 10 is what 99.9% of all applications need
        certain scales might need a different base: f.e 2

        The default setting is 10
        
        :param int base: Base of the engine
        
        .. seealso::
            
            :py:meth:`base()`
        """
        self.__data.base = max([base, 2])
    
    def base(self):
        """
        :return: Base of the scale engine
        
        .. seealso::
            
            :py:meth:`setBase()`
        """
        return self.__data.base


class QwtLinearScaleEngine(QwtScaleEngine):
    """
    A scale engine for linear scales

    The step size will fit into the pattern
    \f$\left\{ 1,2,5\right\} \cdot 10^{n}\f$, where n is an integer.
    """
    
    def __init__(self, base=10):
        super(QwtLinearScaleEngine, self).__init__(base)
    
    def autoScale(self, maxNumSteps, x1, x2, stepSize):
        """
        Align and divide an interval

        :param int maxNumSteps: Max. number of steps
        :param float x1: First limit of the interval (In/Out)
        :param float x2: Second limit of the interval (In/Out)
        :param float stepSize: Step size
        :return: tuple (x1, x2, stepSize)
        
        .. seealso::
            
            :py:meth:`setAttribute()`
        """
        interval = QwtInterval(x1, x2)
        interval = interval.normalized()
        interval.setMinValue(interval.minValue()-self.lowerMargin())
        interval.setMaxValue(interval.maxValue()+self.upperMargin())
        if self.testAttribute(QwtScaleEngine.Symmetric):
            interval = interval.symmetrize(self.reference())
        if self.testAttribute(QwtScaleEngine.IncludeReference):
            interval = interval.extend(self.reference())
        if interval.width() == 0.:
            interval = self.buildInterval(interval.minValue())
        stepSize = divideInterval(interval.width(),
                                  max([maxNumSteps, 1]), self.base())
        if not self.testAttribute(QwtScaleEngine.Floating):
            interval = self.align(interval, stepSize)
        x1 = interval.minValue()
        x2 = interval.maxValue()
        if self.testAttribute(QwtScaleEngine.Inverted):
            x1, x2 = x2, x1
            stepSize = -stepSize
        return x1, x2, stepSize
    
    def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.):
        """
        Calculate a scale division for an interval

        :param float x1: First interval limit
        :param float x2: Second interval limit
        :param int maxMajorSteps: Maximum for the number of major steps
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
        :return: Calculated scale division
        """
        interval = QwtInterval(x1, x2).normalized()
        if interval.width() <= 0:
            return QwtScaleDiv()
        stepSize = abs(stepSize)
        if stepSize == 0.:
            if maxMajorSteps < 1:
                maxMajorSteps = 1
            stepSize = divideInterval(interval.width(), maxMajorSteps,
                                      self.base())
        scaleDiv = QwtScaleDiv()
        if stepSize != 0.:
            ticks = self.buildTicks(interval, stepSize, maxMinorSteps)
            scaleDiv = QwtScaleDiv(interval, ticks)
        if x1 > x2:
            scaleDiv.invert()
        return scaleDiv
        
    def buildTicks(self, interval, stepSize, maxMinorSteps):
        """
        Calculate ticks for an interval
        
        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :param int maxMinorSteps: Maximum number of minor steps
        :return: Calculated ticks
        """
        ticks = [[] for _i in range(QwtScaleDiv.NTickTypes)]
        boundingInterval = self.align(interval, stepSize)
        ticks[QwtScaleDiv.MajorTick] = self.buildMajorTicks(boundingInterval,
                                                            stepSize)
        if maxMinorSteps > 0:
            self.buildMinorTicks(ticks, maxMinorSteps, stepSize)
        for i in range(QwtScaleDiv.NTickTypes):
            ticks[i] = self.strip(ticks[i], interval)
            for j in range(len(ticks[i])):
                if qwtFuzzyCompare(ticks[i][j], 0., stepSize) == 0:
                    ticks[i][j] = 0.
        return ticks
    
    def buildMajorTicks(self, interval, stepSize):
        """
        Calculate major ticks for an interval
        
        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :return: Calculated ticks
        """
        numTicks = min([round(interval.width()/stepSize)+1, 10000])
        if np.isnan(numTicks):
            numTicks = 0
        ticks = [interval.minValue()]
        for i in range(1, int(numTicks-1)):
            ticks += [interval.minValue()+i*stepSize]
        ticks += [interval.maxValue()]
        return ticks
    
    def buildMinorTicks(self, ticks, maxMinorSteps, stepSize):
        """
        Calculate minor ticks for an interval
        
        :param list ticks: Major ticks (returned)
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size
        """
        minStep = qwtStepSize(stepSize, maxMinorSteps, self.base())
        if minStep == 0.:
            return
        numTicks = int(np.ceil(abs(stepSize/minStep))-1)
        medIndex = -1
        if numTicks % 2:
            medIndex = numTicks/2
        for val in ticks[QwtScaleDiv.MajorTick]:
            for k in range(numTicks):
                val += minStep
                alignedValue = val
                if qwtFuzzyCompare(val, 0., stepSize) == 0:
                    alignedValue = 0.
                if k == medIndex:
                    ticks[QwtScaleDiv.MediumTick] += [alignedValue]
                else:
                    ticks[QwtScaleDiv.MinorTick] += [alignedValue]
    
    def align(self, interval, stepSize):
        """
        Align an interval to a step size

        The limits of an interval are aligned that both are integer
        multiples of the step size.
        
        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :return: Aligned interval
        """
        x1 = interval.minValue()
        x2 = interval.maxValue()
        if -DBL_MAX+stepSize <= x1:
            x = floorEps(x1, stepSize)
            if qwtFuzzyCompare(x1, x, stepSize) != 0:
                x1 = x
        if DBL_MAX-stepSize >= x2:
            x = ceilEps(x2, stepSize)
            if qwtFuzzyCompare(x2, x, stepSize) != 0:
                x2 = x
        return QwtInterval(x1, x2)


class QwtLogScaleEngine(QwtScaleEngine):
    """
    A scale engine for logarithmic scales

    The step size is measured in *decades* and the major step size will be 
    adjusted to fit the pattern {1,2,3,5}.10**n, where n is a natural number
    including zero.

    .. warning::
    
        The step size as well as the margins are measured in *decades*.
    """
    def __init__(self, base=10):
        super(QwtLogScaleEngine, self).__init__(base)
        self.setTransformation(QwtLogTransform())
        
    def autoScale(self, maxNumSteps, x1, x2, stepSize):
        """
        Align and divide an interval

        :param int maxNumSteps: Max. number of steps
        :param float x1: First limit of the interval (In/Out)
        :param float x2: Second limit of the interval (In/Out)
        :param float stepSize: Step size
        :return: tuple (x1, x2, stepSize)
        
        .. seealso::
            
            :py:meth:`setAttribute()`
        """
        if x1 > x2:
            x1, x2 = x2, x1
        logBase = self.base()
        interval = QwtInterval(x1/np.power(logBase, self.lowerMargin()),
                               x2*np.power(logBase, self.upperMargin()))
        interval = interval.limited(LOG_MIN, LOG_MAX)
        if interval.maxValue()/interval.minValue() < logBase:
            linearScaler = QwtLinearScaleEngine()
            linearScaler.setAttributes(self.attributes())
            linearScaler.setReference(self.reference())
            linearScaler.setMargins(self.lowerMargin(), self.upperMargin())
            
            x1, x2, stepSize = linearScaler.autoScale(maxNumSteps,
                                                      x1, x2, stepSize)
            
            linearInterval = QwtInterval(x1, x2).normalized()
            linearInterval = linearInterval.limited(LOG_MIN, LOG_MAX)
            
            if linearInterval.maxValue()/linearInterval.minValue() < logBase:
                if stepSize < 0.:
                    stepSize = -qwtLog(logBase, abs(stepSize))
                else:
                    stepSize = qwtLog(logBase, stepSize)
                return x1, x2, stepSize
            
        logRef = 1.
        if self.reference() > LOG_MIN/2:
            logRef = min([self.reference(), LOG_MAX/2])
        
        if self.testAttribute(QwtScaleEngine.Symmetric):
            delta = max([interval.maxValue()/logRef,
                         logRef/interval.minValue()])
            interval.setInterval(logRef/delta, logRef*delta)
        
        if self.testAttribute(QwtScaleEngine.IncludeReference):
            interval = interval.extend(logRef)

        interval = interval.limited(LOG_MIN, LOG_MAX)        
        
        if interval.width() == 0.:
            interval = self.buildInterval(interval.minValue())
        
        stepSize = self.divideInterval(
            qwtLogInterval(logBase, interval).width(), max([maxNumSteps, 1]))
        if stepSize < 1.:
            stepSize = 1.
        
        if not self.testAttribute(QwtScaleEngine.Floating):
            interval = self.align(interval, stepSize)
        
        x1 = interval.minValue()
        x2 = interval.maxValue()
        
        if self.testAttribute(QwtScaleEngine.Inverted):
            x1, x2 = x2, x1
            stepSize = -stepSize

        return x1, x2, stepSize
    
    def divideScale(self, x1, x2, maxMajorSteps, maxMinorSteps, stepSize=0.):
        """
        Calculate a scale division for an interval

        :param float x1: First interval limit
        :param float x2: Second interval limit
        :param int maxMajorSteps: Maximum for the number of major steps
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
        :return: Calculated scale division
        """
        interval = QwtInterval(x1, x2).normalized()
        interval = interval.limited(LOG_MIN, LOG_MAX)
        
        if interval.width() <= 0:
            return QwtScaleDiv()
        
        logBase = self.base()
        
        if interval.maxValue()/interval.minValue() < logBase:
            linearScaler = QwtLinearScaleEngine()
            linearScaler.setAttributes(self.attributes())
            linearScaler.setReference(self.reference())
            linearScaler.setMargins(self.lowerMargin(), self.upperMargin())
            
            if stepSize != 0.:
                if stepSize < 0.:
                    stepSize = -np.power(logBase, -stepSize)
                else:
                    stepSize = np.power(logBase, stepSize)
            
            return linearScaler.divideScale(x1, x2, maxMajorSteps,
                                            maxMinorSteps, stepSize)
        
        stepSize = abs(stepSize)
        if stepSize == 0.:
            if maxMajorSteps < 1:
                maxMajorSteps = 1
            stepSize = self.divideInterval(
                    qwtLogInterval(logBase, interval).width(), maxMajorSteps)
            if stepSize < 1.:
                stepSize = 1.
        
        scaleDiv = QwtScaleDiv()
        if stepSize != 0.:
            ticks = self.buildTicks(interval, stepSize, maxMinorSteps)
            scaleDiv = QwtScaleDiv(interval, ticks)
        
        if x1 > x2:
            scaleDiv.invert()
        
        return scaleDiv
    
    def buildTicks(self, interval, stepSize, maxMinorSteps):
        """
        Calculate ticks for an interval
        
        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :param int maxMinorSteps: Maximum number of minor steps
        :return: Calculated ticks
        """
        ticks = [[] for _i in range(QwtScaleDiv.NTickTypes)]
        boundingInterval = self.align(interval, stepSize)
        ticks[QwtScaleDiv.MajorTick] = self.buildMajorTicks(boundingInterval,
                                                            stepSize)
        if maxMinorSteps > 0:
            self.buildMinorTicks(ticks, maxMinorSteps, stepSize)
        for i in range(QwtScaleDiv.NTickTypes):
            ticks[i] = self.strip(ticks[i], interval)
        return ticks
    
    def buildMajorTicks(self, interval, stepSize):
        """
        Calculate major ticks for an interval
        
        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :return: Calculated ticks
        """
        width = qwtLogInterval(self.base(), interval).width()
        numTicks = min([int(round(width/stepSize))+1, 10000])

        lxmin = np.log(interval.minValue())
        lxmax = np.log(interval.maxValue())
        lstep = (lxmax-lxmin)/float(numTicks-1)

        ticks = [interval.minValue()]
        for i in range(1, numTicks-1):
            ticks += [np.exp(lxmin+float(i)*lstep)]
        ticks += [interval.maxValue()]
        return ticks

    def buildMinorTicks(self, ticks, maxMinorSteps, stepSize):
        """
        Calculate minor ticks for an interval
        
        :param list ticks: Major ticks (returned)
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size
        """
        logBase = self.base()
        
        if stepSize < 1.1:
            minStep = self.divideInterval(stepSize, maxMinorSteps+1)
            if minStep == 0.:
                return
            
            numSteps = int(round(stepSize/minStep))
            
            mediumTickIndex = -1
            if numSteps > 2 and numSteps % 2 == 0:
                mediumTickIndex = numSteps/2
            
            for v in ticks[QwtScaleDiv.MajorTick]:
                s = logBase/numSteps
                if s >= 1.:
                    if not qFuzzyCompare(s, 1.):
                        ticks[QwtScaleDiv.MinorTick] += [v*s]
                    for j in range(2, numSteps):
                        ticks[QwtScaleDiv.MinorTick] += [v*j*s]
                else:
                    for j in range(1, numSteps):
                        tick = v + j*v*(logBase-1)/numSteps
                        if j == mediumTickIndex:
                            ticks[QwtScaleDiv.MediumTick] += [tick]
                        else:
                            ticks[QwtScaleDiv.MinorTick] += [tick]
                            
        else:
            minStep = self.divideInterval(stepSize, maxMinorSteps)
            if minStep == 0.:
                return
            
            if minStep < 1.:
                minStep = 1.
            
            numTicks = int(round(stepSize/minStep))-1
            
            if qwtFuzzyCompare((numTicks+1)*minStep, stepSize, stepSize) > 0:
                numTicks = 0
            
            if numTicks < 1:
                return
            
            mediumTickIndex = -1
            if numTicks > 2 and numTicks % 2:
                mediumTickIndex = numTicks/2
            
            minFactor = max([np.power(logBase, minStep), float(logBase)])
            
            for tick in ticks[QwtScaleDiv.MajorTick]:
                for j in range(numTicks):
                    tick *= minFactor
                    if j == mediumTickIndex:
                        ticks[QwtScaleDiv.MediumTick] += [tick]
                    else:
                        ticks[QwtScaleDiv.MinorTick] += [tick]

    def align(self, interval, stepSize):
        """
        Align an interval to a step size

        The limits of an interval are aligned that both are integer
        multiples of the step size.
        
        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :return: Aligned interval
        """
        intv = qwtLogInterval(self.base(), interval)
        
        x1 = floorEps(intv.minValue(), stepSize)
        if qwtFuzzyCompare(interval.minValue(), x1, stepSize) == 0:
            x1 = interval.minValue()
        
        x2 = ceilEps(intv.maxValue(), stepSize)
        if qwtFuzzyCompare(interval.maxValue(), x2, stepSize) == 0:
            x2 = interval.maxValue()
        
        return qwtPowInterval(self.base(), QwtInterval(x1, x2))
        
        
