diff options
Diffstat (limited to 'silx/gui/plot/_utils')
-rw-r--r-- | silx/gui/plot/_utils/__init__.py | 104 | ||||
-rw-r--r-- | silx/gui/plot/_utils/panzoom.py | 156 | ||||
-rw-r--r-- | silx/gui/plot/_utils/setup.py | 42 | ||||
-rw-r--r-- | silx/gui/plot/_utils/test/__init__.py | 41 | ||||
-rw-r--r-- | silx/gui/plot/_utils/test/test_ticklayout.py | 78 | ||||
-rw-r--r-- | silx/gui/plot/_utils/ticklayout.py | 224 |
6 files changed, 645 insertions, 0 deletions
diff --git a/silx/gui/plot/_utils/__init__.py b/silx/gui/plot/_utils/__init__.py new file mode 100644 index 0000000..355bc02 --- /dev/null +++ b/silx/gui/plot/_utils/__init__.py @@ -0,0 +1,104 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2004-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Miscellaneous utility functions for the Plot""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "21/03/2017" + + +import numpy + +from .panzoom import FLOAT32_SAFE_MIN, FLOAT32_MINPOS, FLOAT32_SAFE_MAX +from .panzoom import applyZoomToPlot, applyPan + + +def clipColormapLogRange(colormap): + """Clip colormap vmin and vmax to 1, 10 if normalization is 'log' and vmin + or vmax <1 + + :param dict colormap: the colormap for which we want to clip vmin and vmax + """ + if colormap['normalization'] is 'log': + if colormap['vmin'] < 1. or colormap['vmax'] < 1.: + colormap['vmin'], colormap['vmax'] = 1., 10. + + +def addMarginsToLimits(margins, isXLog, isYLog, + xMin, xMax, yMin, yMax, y2Min=None, y2Max=None): + """Returns updated limits by extending them with margins. + + :param margins: The ratio of the margins to add or None for no margins. + :type margins: A 4-tuple of floats as + (xMinMargin, xMaxMargin, yMinMargin, yMaxMargin) + + :return: The updated limits + :rtype: tuple of 4 or 6 floats: Either (xMin, xMax, yMin, yMax) or + (xMin, xMax, yMin, yMax, y2Min, y2Max) if y2Min and y2Max + are provided. + """ + if margins is not None: + xMinMargin, xMaxMargin, yMinMargin, yMaxMargin = margins + + if not isXLog: + xRange = xMax - xMin + xMin -= xMinMargin * xRange + xMax += xMaxMargin * xRange + + elif xMin > 0. and xMax > 0.: # Log scale + # Do not apply margins if limits < 0 + xMinLog, xMaxLog = numpy.log10(xMin), numpy.log10(xMax) + xRangeLog = xMaxLog - xMinLog + xMin = pow(10., xMinLog - xMinMargin * xRangeLog) + xMax = pow(10., xMaxLog + xMaxMargin * xRangeLog) + + if not isYLog: + yRange = yMax - yMin + yMin -= yMinMargin * yRange + yMax += yMaxMargin * yRange + elif yMin > 0. and yMax > 0.: # Log scale + # Do not apply margins if limits < 0 + yMinLog, yMaxLog = numpy.log10(yMin), numpy.log10(yMax) + yRangeLog = yMaxLog - yMinLog + yMin = pow(10., yMinLog - yMinMargin * yRangeLog) + yMax = pow(10., yMaxLog + yMaxMargin * yRangeLog) + + if y2Min is not None and y2Max is not None: + if not isYLog: + yRange = y2Max - y2Min + y2Min -= yMinMargin * yRange + y2Max += yMaxMargin * yRange + elif y2Min > 0. and y2Max > 0.: # Log scale + # Do not apply margins if limits < 0 + yMinLog, yMaxLog = numpy.log10(y2Min), numpy.log10(y2Max) + yRangeLog = yMaxLog - yMinLog + y2Min = pow(10., yMinLog - yMinMargin * yRangeLog) + y2Max = pow(10., yMaxLog + yMaxMargin * yRangeLog) + + if y2Min is None or y2Max is None: + return xMin, xMax, yMin, yMax + else: + return xMin, xMax, yMin, yMax, y2Min, y2Max + diff --git a/silx/gui/plot/_utils/panzoom.py b/silx/gui/plot/_utils/panzoom.py new file mode 100644 index 0000000..bec31df --- /dev/null +++ b/silx/gui/plot/_utils/panzoom.py @@ -0,0 +1,156 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2004-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""Functions to apply pan and zoom on a Plot""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "21/03/2017" + + +import math + +import numpy + + +# Float 32 info ############################################################### +# Using min/max value below limits of float32 +# so operation with such value (e.g., max - min) do not overflow + +FLOAT32_SAFE_MIN = -1e37 +FLOAT32_MINPOS = numpy.finfo(numpy.float32).tiny +FLOAT32_SAFE_MAX = 1e37 +# TODO double support + + +def scale1DRange(min_, max_, center, scale, isLog): + """Scale a 1D range given a scale factor and an center point. + + Keeps the values in a smaller range than float32. + + :param float min_: The current min value of the range. + :param float max_: The current max value of the range. + :param float center: The center of the zoom (i.e., invariant point). + :param float scale: The scale to use for zoom + :param bool isLog: Whether using log scale or not. + :return: The zoomed range. + :rtype: tuple of 2 floats: (min, max) + """ + if isLog: + # Min and center can be < 0 when + # autoscale is off and switch to log scale + # max_ < 0 should not happen + min_ = numpy.log10(min_) if min_ > 0. else FLOAT32_MINPOS + center = numpy.log10(center) if center > 0. else FLOAT32_MINPOS + max_ = numpy.log10(max_) if max_ > 0. else FLOAT32_MINPOS + + if min_ == max_: + return min_, max_ + + offset = (center - min_) / (max_ - min_) + range_ = (max_ - min_) / scale + newMin = center - offset * range_ + newMax = center + (1. - offset) * range_ + + if isLog: + # No overflow as exponent is log10 of a float32 + newMin = pow(10., newMin) + newMax = pow(10., newMax) + newMin = numpy.clip(newMin, FLOAT32_MINPOS, FLOAT32_SAFE_MAX) + newMax = numpy.clip(newMax, FLOAT32_MINPOS, FLOAT32_SAFE_MAX) + else: + newMin = numpy.clip(newMin, FLOAT32_SAFE_MIN, FLOAT32_SAFE_MAX) + newMax = numpy.clip(newMax, FLOAT32_SAFE_MIN, FLOAT32_SAFE_MAX) + return newMin, newMax + + +def applyZoomToPlot(plot, scaleF, center=None): + """Zoom in/out plot given a scale and a center point. + + :param plot: The plot on which to apply zoom. + :param float scaleF: Scale factor of zoom. + :param center: (x, y) coords in pixel coordinates of the zoom center. + :type center: 2-tuple of float + """ + xMin, xMax = plot.getGraphXLimits() + yMin, yMax = plot.getGraphYLimits() + + if center is None: + left, top, width, height = plot.getPlotBoundsInPixels() + cx, cy = left + width // 2, top + height // 2 + else: + cx, cy = center + + dataCenterPos = plot.pixelToData(cx, cy) + assert dataCenterPos is not None + + xMin, xMax = scale1DRange(xMin, xMax, dataCenterPos[0], scaleF, + plot.isXAxisLogarithmic()) + + yMin, yMax = scale1DRange(yMin, yMax, dataCenterPos[1], scaleF, + plot.isYAxisLogarithmic()) + + dataPos = plot.pixelToData(cx, cy, axis="right") + assert dataPos is not None + y2Center = dataPos[1] + y2Min, y2Max = plot.getGraphYLimits(axis="right") + y2Min, y2Max = scale1DRange(y2Min, y2Max, y2Center, scaleF, + plot.isYAxisLogarithmic()) + + plot.setLimits(xMin, xMax, yMin, yMax, y2Min, y2Max) + + +def applyPan(min_, max_, panFactor, isLog10): + """Returns a new range with applied panning. + + Moves the range according to panFactor. + If isLog10 is True, converts to log10 before moving. + + :param float min_: Min value of the data range to pan. + :param float max_: Max value of the data range to pan. + Must be >= min. + :param float panFactor: Signed proportion of the range to use for pan. + :param bool isLog10: True if log10 scale, False if linear scale. + :return: New min and max value with pan applied. + :rtype: 2-tuple of float. + """ + if isLog10 and min_ > 0.: + # Negative range and log scale can happen with matplotlib + logMin, logMax = math.log10(min_), math.log10(max_) + logOffset = panFactor * (logMax - logMin) + newMin = pow(10., logMin + logOffset) + newMax = pow(10., logMax + logOffset) + + # Takes care of out-of-range values + if newMin > 0. and newMax < float('inf'): + min_, max_ = newMin, newMax + + else: + offset = panFactor * (max_ - min_) + newMin, newMax = min_ + offset, max_ + offset + + # Takes care of out-of-range values + if newMin > - float('inf') and newMax < float('inf'): + min_, max_ = newMin, newMax + return min_, max_ diff --git a/silx/gui/plot/_utils/setup.py b/silx/gui/plot/_utils/setup.py new file mode 100644 index 0000000..0271745 --- /dev/null +++ b/silx/gui/plot/_utils/setup.py @@ -0,0 +1,42 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "21/03/2017" + + +from numpy.distutils.misc_util import Configuration + + +def configuration(parent_package='', top_path=None): + config = Configuration('_utils', parent_package, top_path) + config.add_subpackage('test') + return config + + +if __name__ == "__main__": + from numpy.distutils.core import setup + + setup(configuration=configuration) diff --git a/silx/gui/plot/_utils/test/__init__.py b/silx/gui/plot/_utils/test/__init__.py new file mode 100644 index 0000000..4a443ac --- /dev/null +++ b/silx/gui/plot/_utils/test/__init__.py @@ -0,0 +1,41 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ + +from __future__ import absolute_import, division, unicode_literals + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "18/10/2016" + + +import unittest + +from .test_ticklayout import suite as test_ticklayout_suite + + +def suite(): + testsuite = unittest.TestSuite() + testsuite.addTest(test_ticklayout_suite()) + return testsuite diff --git a/silx/gui/plot/_utils/test/test_ticklayout.py b/silx/gui/plot/_utils/test/test_ticklayout.py new file mode 100644 index 0000000..8c67620 --- /dev/null +++ b/silx/gui/plot/_utils/test/test_ticklayout.py @@ -0,0 +1,78 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2015-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ + +from __future__ import absolute_import, division, unicode_literals + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "18/10/2016" + + +import unittest + +from silx.test.utils import ParametricTestCase + +from silx.gui.plot._utils import ticklayout + + +class TestTickLayout(ParametricTestCase): + """Test ticks layout algorithms""" + + def testNiceNumbers(self): + """Minimalistic tests of :func:`niceNumbers`""" + tests = { # (vmin, vmax): ref_ticks + (0.5, 10.5): (0.0, 12.0, 2.0, 0), + (10000., 10000.5): (10000.0, 10000.5, 0.1, 1), + (0.001, 0.005): (0.001, 0.005, 0.001, 3) + } + + for (vmin, vmax), ref_ticks in tests.items(): + with self.subTest(vmin=vmin, vmax=vmax): + ticks = ticklayout.niceNumbers(vmin, vmax) + self.assertEqual(ticks, ref_ticks) + + def testNiceNumbersLog(self): + """Minimalistic tests of :func:`niceNumbersForLog10`""" + tests = { # (log10(min), log10(max): ref_ticks + (0., 3.): (0, 3, 1, 0), + (-3., 3): (-3, 3, 1, 0), + (-32., 0.): (-36, 0, 6, 0) + } + + for (vmin, vmax), ref_ticks in tests.items(): + with self.subTest(vmin=vmin, vmax=vmax): + ticks = ticklayout.niceNumbersForLog10(vmin, vmax) + self.assertEqual(ticks, ref_ticks) + + +def suite(): + testsuite = unittest.TestSuite() + testsuite.addTest( + unittest.defaultTestLoader.loadTestsFromTestCase(TestTickLayout)) + return testsuite + + +if __name__ == '__main__': + unittest.main() diff --git a/silx/gui/plot/_utils/ticklayout.py b/silx/gui/plot/_utils/ticklayout.py new file mode 100644 index 0000000..5f4b636 --- /dev/null +++ b/silx/gui/plot/_utils/ticklayout.py @@ -0,0 +1,224 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2014-2017 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This module implements labels layout on graph axes.""" + +from __future__ import absolute_import, division, unicode_literals + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "18/10/2016" + + +import math + + +# utils ####################################################################### + +def numberOfDigits(tickSpacing): + """Returns the number of digits to display for text label. + + :param float tickSpacing: Step between ticks in data space. + :return: Number of digits to show for labels. + :rtype: int + """ + nfrac = int(-math.floor(math.log10(tickSpacing))) + if nfrac < 0: + nfrac = 0 + return nfrac + + +# Nice Numbers ################################################################ + +def _niceNum(value, isRound=False): + expvalue = math.floor(math.log10(value)) + frac = value/pow(10., expvalue) + if isRound: + if frac < 1.5: + nicefrac = 1. + elif frac < 3.: + nicefrac = 2. + elif frac < 7.: + nicefrac = 5. + else: + nicefrac = 10. + else: + if frac <= 1.: + nicefrac = 1. + elif frac <= 2.: + nicefrac = 2. + elif frac <= 5.: + nicefrac = 5. + else: + nicefrac = 10. + return nicefrac * pow(10., expvalue) + + +def niceNumbers(vMin, vMax, nTicks=5): + """Returns tick positions. + + This function implements graph labels layout using nice numbers + by Paul Heckbert from "Graphics Gems", Academic Press, 1990. + See `C code <http://tog.acm.org/resources/GraphicsGems/gems/Label.c>`_. + + :param float vMin: The min value on the axis + :param float vMax: The max value on the axis + :param int nTicks: The number of ticks to position + :returns: min, max, increment value of tick positions and + number of fractional digit to show + :rtype: tuple + """ + vrange = _niceNum(vMax - vMin, False) + spacing = _niceNum(vrange / nTicks, True) + graphmin = math.floor(vMin / spacing) * spacing + graphmax = math.ceil(vMax / spacing) * spacing + nfrac = numberOfDigits(spacing) + return graphmin, graphmax, spacing, nfrac + + +def _frange(start, stop, step): + """range for float (including stop).""" + assert step >= 0. + while start <= stop: + yield start + start += step + + +def ticks(vMin, vMax, nbTicks=5): + """Returns tick positions and labels using nice numbers algorithm. + + This enforces ticks to be within [vMin, vMax] range. + It returns at least 2 ticks. + + :param float vMin: The min value on the axis + :param float vMax: The max value on the axis + :param int nbTicks: The number of ticks to position + :returns: tick positions and corresponding text labels + :rtype: 2-tuple: list of float, list of string + """ + start, end, step, nfrac = niceNumbers(vMin, vMax, nbTicks) + positions = [t for t in _frange(start, end, step) if vMin <= t <= vMax] + + # Makes sure there is at least 2 ticks + if len(positions) < 2: + positions = [vMin, vMax] + nfrac = numberOfDigits(vMax - vMin) + + # Generate labels + format_ = '%g' if nfrac == 0 else '%.{}f'.format(nfrac) + labels = [format_ % tick for tick in positions] + return positions, labels + + +def niceNumbersAdaptative(vMin, vMax, axisLength, tickDensity): + """Returns tick positions using :func:`niceNumbers` and a + density of ticks. + + axisLength and tickDensity are based on the same unit (e.g., pixel). + + :param float vMin: The min value on the axis + :param float vMax: The max value on the axis + :param float axisLength: The length of the axis. + :param float tickDensity: The density of ticks along the axis. + :returns: min, max, increment value of tick positions and + number of fractional digit to show + :rtype: tuple + """ + # At least 2 ticks + nticks = max(2, int(round(tickDensity * axisLength))) + tickmin, tickmax, step, nfrac = niceNumbers(vMin, vMax, nticks) + + return tickmin, tickmax, step, nfrac + + +# Nice Numbers for log scale ################################################## + +def niceNumbersForLog10(minLog, maxLog, nTicks=5): + """Return tick positions for logarithmic scale + + :param float minLog: log10 of the min value on the axis + :param float maxLog: log10 of the max value on the axis + :param int nTicks: The number of ticks to position + :returns: log10 of min, max, increment value of tick positions and + number of fractional digit to show + :rtype: tuple of int + """ + graphminlog = math.floor(minLog) + graphmaxlog = math.ceil(maxLog) + rangelog = graphmaxlog - graphminlog + + if rangelog <= nTicks: + spacing = 1. + else: + spacing = math.floor(rangelog / nTicks) + + graphminlog = math.floor(graphminlog / spacing) * spacing + graphmaxlog = math.ceil(graphmaxlog / spacing) * spacing + + nfrac = numberOfDigits(spacing) + + return int(graphminlog), int(graphmaxlog), int(spacing), nfrac + + +def niceNumbersAdaptativeForLog10(vMin, vMax, axisLength, tickDensity): + """Returns tick positions using :func:`niceNumbers` and a + density of ticks. + + axisLength and tickDensity are based on the same unit (e.g., pixel). + + :param float vMin: The min value on the axis + :param float vMax: The max value on the axis + :param float axisLength: The length of the axis. + :param float tickDensity: The density of ticks along the axis. + :returns: log10 of min, max, increment value of tick positions and + number of fractional digit to show + :rtype: tuple + """ + # At least 2 ticks + nticks = max(2, int(round(tickDensity * axisLength))) + tickmin, tickmax, step, nfrac = niceNumbersForLog10(vMin, vMax, nticks) + + return tickmin, tickmax, step, nfrac + + +def computeLogSubTicks(ticks, lowBound, highBound): + """Return the sub ticks for the log scale for all given ticks if subtick + is in [lowBound, highBound] + + :param ticks: log10 of the ticks + :param lowBound: the lower boundary of ticks + :param highBound: the higher boundary of ticks + :return: all the sub ticks contained in ticks (log10) + """ + if len(ticks) < 1: + return [] + + res = [] + for logPos in ticks: + dataOrigPos = logPos + for index in range(2, 10): + dataPos = dataOrigPos * index + if lowBound <= dataPos <= highBound: + res.append(dataPos) + return res |