summaryrefslogtreecommitdiff
path: root/silx/gui/plot/ColorBar.py
diff options
context:
space:
mode:
authorPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2017-08-18 14:48:52 +0200
committerPicca Frédéric-Emmanuel <picca@synchrotron-soleil.fr>2017-08-18 14:48:52 +0200
commitf7bdc2acff3c13a6d632c28c4569690ab106eed7 (patch)
tree9d67cdb7152ee4e711379e03fe0546c7c3b97303 /silx/gui/plot/ColorBar.py
Import Upstream version 0.5.0+dfsg
Diffstat (limited to 'silx/gui/plot/ColorBar.py')
-rw-r--r--silx/gui/plot/ColorBar.py790
1 files changed, 790 insertions, 0 deletions
diff --git a/silx/gui/plot/ColorBar.py b/silx/gui/plot/ColorBar.py
new file mode 100644
index 0000000..93e3c36
--- /dev/null
+++ b/silx/gui/plot/ColorBar.py
@@ -0,0 +1,790 @@
+# 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.
+#
+# ###########################################################################*/
+"""Module containing several widgets associated to a colormap.
+"""
+
+__authors__ = ["H. Payno", "T. Vincent"]
+__license__ = "MIT"
+__date__ = "11/04/2017"
+
+
+import logging
+import numpy
+from ._utils import ticklayout
+from ._utils import clipColormapLogRange
+
+
+from .. import qt
+from silx.gui.plot import Colors
+
+_logger = logging.getLogger(__name__)
+
+
+class ColorBarWidget(qt.QWidget):
+ """Colorbar widget displaying a colormap
+
+ It uses a description of colormap as dict compatible with :class:`Plot`.
+
+ .. image:: img/linearColorbar.png
+ :width: 80px
+ :align: center
+
+ To run the following sample code, a QApplication must be initialized.
+
+ >>> from silx.gui.plot import Plot2D
+ >>> from silx.gui.plot.ColorBar import ColorBarWidget
+
+ >>> plot = Plot2D() # Create a plot widget
+ >>> plot.show()
+
+ >>> colorbar = ColorBarWidget(plot=plot, legend='Colormap') # Associate the colorbar with it
+ >>> colorbar.show()
+
+ Initializer parameters:
+
+ :param parent: See :class:`QWidget`
+ :param plot: PlotWidget the colorbar is attached to (optional)
+ :param str legend: the label to set to the colormap
+ """
+
+ def __init__(self, parent=None, plot=None, legend=None):
+ super(ColorBarWidget, self).__init__(parent)
+ self._plot = None
+
+ self.__buildGUI()
+ self.setLegend(legend)
+ self.setPlot(plot)
+
+ def __buildGUI(self):
+ self.setLayout(qt.QHBoxLayout())
+
+ # create color scale widget
+ self._colorScale = ColorScaleBar(parent=self,
+ colormap=None)
+ self.layout().addWidget(self._colorScale)
+
+ # legend (is the right group)
+ self.legend = _VerticalLegend('', self)
+ self.layout().addWidget(self.legend)
+
+ self.layout().setSizeConstraint(qt.QLayout.SetMinAndMaxSize)
+ self.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
+ self.layout().setContentsMargins(0, 0, 0, 0)
+
+ def getPlot(self):
+ """Returns the :class:`Plot` associated to this widget or None"""
+ return self._plot
+
+ def setPlot(self, plot):
+ """Associate a plot to the ColorBar
+
+ :param plot: the plot to associate with the colorbar. If None will remove
+ any connection with a previous plot.
+ """
+ # removing previous plot if any
+ if self._plot is not None:
+ self._plot.sigActiveImageChanged.disconnect(self._activeImageChanged)
+
+ # setting the new plot
+ self._plot = plot
+ if self._plot is not None:
+ self._plot.sigActiveImageChanged.connect(self._activeImageChanged)
+ self._activeImageChanged(self._plot.getActiveImage(just_legend=True))
+
+ def getColormap(self):
+ """Return the colormap displayed in the colorbar as a dict.
+
+ It returns None if no colormap is set.
+ See :class:`silx.gui.plot.Plot` documentation for the description of the colormap
+ dict description.
+ """
+ return self._colormap.copy()
+
+ def setColormap(self, colormap):
+ """Set the colormap to be displayed.
+
+ :param dict colormap: The colormap to apply on the ColorBarWidget
+ """
+ self._colormap = colormap
+ if self._colormap is None:
+ return
+
+ if self._colormap['normalization'] not in ('log', 'linear'):
+ raise ValueError('Wrong normalization %s' % self._colormap['normalization'])
+
+ if self._colormap['normalization'] is 'log':
+ if self._colormap['vmin'] < 1. or self._colormap['vmax'] < 1.:
+ _logger.warning('Log colormap with bound <= 1: changing bounds.')
+ clipColormapLogRange(colormap)
+
+ self.getColorScaleBar().setColormap(self._colormap)
+
+ def setLegend(self, legend):
+ """Set the legend displayed along the colorbar
+
+ :param str legend: The label
+ """
+ if legend is None or legend == "":
+ self.legend.hide()
+ self.legend.setText("")
+ else:
+ assert(type(legend) is str)
+ self.legend.show()
+ self.legend.setText(legend)
+
+ def getLegend(self):
+ """
+ Returns the legend displayed along the colorbar
+
+ :return: return the legend displayed along the colorbar
+ :rtype: str
+ """
+ return self.legend.getText()
+
+ def _activeImageChanged(self, legend):
+ """Handle plot active curve changed"""
+ if legend is None: # No active image, display default colormap
+ self._syncWithDefaultColormap()
+ return
+
+ # Sync with active image
+ image = self._plot.getActiveImage().getData(copy=False)
+
+ # RGB(A) image, display default colormap
+ if image.ndim != 2:
+ self._syncWithDefaultColormap()
+ return
+
+ # data image, sync with image colormap
+ # do we need the copy here : used in the case we are changing
+ # vmin and vmax but should have already be done by the plot
+ cmap = self._plot.getActiveImage().getColormap().copy()
+ if cmap['autoscale']:
+ if cmap['normalization'] == 'log':
+ data = image[
+ numpy.logical_and(image > 0, numpy.isfinite(image))]
+ else:
+ data = image[numpy.isfinite(image)]
+ cmap['vmin'], cmap['vmax'] = data.min(), data.max()
+
+ self.setColormap(cmap)
+
+ def _defaultColormapChanged(self):
+ """Handle plot default colormap changed"""
+ if self._plot.getActiveImage() is None:
+ # No active image, take default colormap update into account
+ self._syncWithDefaultColormap()
+
+ def _syncWithDefaultColormap(self):
+ """Update colorbar according to plot default colormap"""
+ self.setColormap(self._plot.getDefaultColormap())
+
+ def getColorScaleBar(self):
+ """
+
+ :return: return the :class:`ColorScaleBar` used to display ColorScale
+ and ticks"""
+ return self._colorScale
+
+
+class _VerticalLegend(qt.QLabel):
+ """Display vertically the given text
+ """
+ def __init__(self, text, parent=None):
+ """
+
+ :param text: the legend
+ :param parent: the Qt parent if any
+ """
+ qt.QLabel.__init__(self, text, parent)
+ self.setLayout(qt.QVBoxLayout())
+ self.layout().setContentsMargins(0, 0, 0, 0)
+
+ def paintEvent(self, event):
+ painter = qt.QPainter(self)
+ painter.setFont(self.font())
+
+ painter.translate(0, self.rect().height())
+ painter.rotate(270)
+ newRect = qt.QRect(0, 0, self.rect().height(), self.rect().width())
+
+ painter.drawText(newRect, qt.Qt.AlignHCenter, self.text())
+
+ fm = qt.QFontMetrics(self.font())
+ preferedHeight = fm.width(self.text())
+ preferedWidth = fm.height()
+ self.setFixedWidth(preferedWidth)
+ self.setMinimumHeight(preferedHeight)
+
+
+class ColorScaleBar(qt.QWidget):
+ """This class is making the composition of a :class:`_ColorScale` and a
+ :class:`_TickBar`.
+
+ It is the simplest widget displaying ticks and colormap gradient.
+
+ .. image:: img/colorScaleBar.png
+ :width: 150px
+ :align: center
+
+ To run the following sample code, a QApplication must be initialized.
+
+ >>> colormap={'name':'gray',
+ ... 'normalization':'log',
+ ... 'vmin':1,
+ ... 'vmax':100000,
+ ... 'autoscale':False
+ ... }
+ >>> colorscale = ColorScaleBar(parent=None,
+ ... colormap=colormap )
+ >>> colorscale.show()
+
+ Initializer parameters :
+
+ :param colormap: the colormap to be displayed
+ :param parent: the Qt parent if any
+ :param displayTicksValues: display the ticks value or only the '-'
+ """
+
+ _TEXT_MARGIN = 5
+ """The tick bar need a margin to display all labels at the correct place.
+ So the ColorScale should have the same margin in order for both to fit"""
+
+ _MIN_LIM_SCI_FORM = -1000
+ """Used for the min and max label to know when we should display it under
+ the scientific form"""
+
+ _MAX_LIM_SCI_FORM = 1000
+ """Used for the min and max label to know when we should display it under
+ the scientific form"""
+
+ def __init__(self, parent=None, colormap=None, displayTicksValues=True):
+ super(ColorScaleBar, self).__init__(parent)
+
+ self.minVal = None
+ """Value set to the _minLabel"""
+ self.maxVal = None
+ """Value set to the _maxLabel"""
+
+ self.setLayout(qt.QGridLayout())
+
+ # create the left side group (ColorScale)
+ self.colorScale = _ColorScale(colormap=colormap,
+ parent=self,
+ margin=ColorScaleBar._TEXT_MARGIN)
+
+ self.tickbar = _TickBar(vmin=colormap['vmin'] if colormap else 0.0,
+ vmax=colormap['vmax'] if colormap else 1.0,
+ norm=colormap['normalization'] if colormap else 'linear',
+ parent=self,
+ displayValues=displayTicksValues,
+ margin=ColorScaleBar._TEXT_MARGIN)
+
+ self.layout().addWidget(self.tickbar, 1, 0)
+ self.layout().addWidget(self.colorScale, 1, 1)
+
+ self.layout().setContentsMargins(0, 0, 0, 0)
+ self.layout().setSpacing(0)
+
+ # max label
+ self._maxLabel = qt.QLabel(str(1.0), parent=self)
+ self._maxLabel.setAlignment(qt.Qt.AlignHCenter)
+ self._maxLabel.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum)
+ self.layout().addWidget(self._maxLabel, 0, 1)
+
+ # min label
+ self._minLabel = qt.QLabel(str(0.0), parent=self)
+ self._minLabel.setAlignment(qt.Qt.AlignHCenter)
+ self._minLabel.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Minimum)
+ self.layout().addWidget(self._minLabel, 2, 1)
+
+ def getTickBar(self):
+ """
+
+ :return: the instanciation of the :class:`_TickBar`
+ """
+ return self.tickbar
+
+ def getColorScale(self):
+ """
+
+ :return: the instanciation of the :class:`_ColorScale`
+ """
+ return self.colorScale
+
+ def setColormap(self, colormap):
+ """Set the new colormap to be displayed
+
+ :param dict colormap: the colormap to set
+ """
+ if colormap is not None:
+ self.colorScale.setColormap(colormap)
+
+ self.tickbar.update(vmin=colormap['vmin'],
+ vmax=colormap['vmax'],
+ norm=colormap['normalization'])
+
+ self._setMinMaxLabels(colormap['vmin'], colormap['vmax'])
+
+ def setMinMaxVisible(self, val=True):
+ """Change visibility of the min label and the max label
+
+ :param val: if True, set the labels visible, otherwise set it not visible
+ """
+ self._maxLabel.show() if val is True else self._maxLabel.hide()
+ self._minLabel.show() if val is True else self._minLabel.hide()
+
+ def _updateMinMax(self):
+ """Update the min and max label if we are in the case of the
+ configuration 'minMaxValueOnly'"""
+ if self._minLabel is not None and self._maxLabel is not None:
+ if self.minVal is not None:
+ if ColorScaleBar._MIN_LIM_SCI_FORM <= self.minVal <= ColorScaleBar._MAX_LIM_SCI_FORM:
+ self._minLabel.setText(str(self.minVal))
+ else:
+ self._minLabel.setText("{0:.0e}".format(self.minVal))
+ if self.maxVal is not None:
+ if ColorScaleBar._MIN_LIM_SCI_FORM <= self.maxVal <= ColorScaleBar._MAX_LIM_SCI_FORM:
+ self._maxLabel.setText(str(self.maxVal))
+ else:
+ self._maxLabel.setText("{0:.0e}".format(self.maxVal))
+
+ def _setMinMaxLabels(self, minVal, maxVal):
+ """Change the value of the min and max labels to be displayed.
+
+ :param minVal: the minimal value of the TickBar (not str)
+ :param maxVal: the maximal value of the TickBar (not str)
+ """
+ # bad hack to try to display has much information as possible
+ self.minVal = minVal
+ self.maxVal = maxVal
+ self._updateMinMax()
+
+ def resizeEvent(self, event):
+ qt.QWidget.resizeEvent(self, event)
+ self._updateMinMax()
+
+
+class _ColorScale(qt.QWidget):
+ """Widget displaying the colormap colorScale.
+
+ Show matching value between the gradient color (from the colormap) at mouse
+ position and value.
+
+ .. image:: img/colorScale.png
+ :width: 20px
+ :align: center
+
+
+ To run the following sample code, a QApplication must be initialized.
+
+ >>> colormap={'name':'viridis',
+ ... 'normalization':'log',
+ ... 'vmin':1,
+ ... 'vmax':100000,
+ ... 'autoscale':False
+ ... }
+ >>> colorscale = ColorScale(parent=None,
+ ... colormap=colormap)
+ >>> colorscale.show()
+
+ Initializer parameters :
+
+ :param colormap: the colormap to be displayed
+ :param parent: the Qt parent if any
+ :param int margin: the top and left margin to apply.
+
+ .. warning:: Value drawing will be
+ done at the center of ticks. So if no margin is done your values
+ drawing might not be fully done for extrems values.
+ """
+
+ _NB_CONTROL_POINTS = 256
+
+ def __init__(self, colormap, parent=None, margin=5):
+ qt.QWidget.__init__(self, parent)
+ self.colormap = None
+ self.setColormap(colormap)
+
+ self.setLayout(qt.QVBoxLayout())
+ self.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding)
+ # needed to get the mouse event without waiting for button click
+ self.setMouseTracking(True)
+ self.setMargin(margin)
+ self.setContentsMargins(0, 0, 0, 0)
+
+ def setColormap(self, colormap):
+ """Set the new colormap to be displayed
+
+ :param dict colormap: the colormap to set
+ """
+ if colormap is None:
+ return
+
+ if colormap['normalization'] not in ('log', 'linear'):
+ raise ValueError("Unrecognized normalization, should be 'linear' or 'log'")
+
+ if colormap['normalization'] is 'log':
+ if not (colormap['vmin'] > 0 and colormap['vmax'] > 0):
+ raise ValueError('vmin and vmax should be positives')
+ self.colormap = colormap
+ self._computeColorPoints()
+
+ def _computeColorPoints(self):
+ """Compute the color points for the gradient
+ """
+ if self.colormap is None:
+ return
+
+ vmin = self.colormap['vmin']
+ vmax = self.colormap['vmax']
+ steps = (vmax - vmin)/float(_ColorScale._NB_CONTROL_POINTS)
+ self.ctrPoints = numpy.arange(vmin, vmax, steps)
+ self.colorsCtrPts = Colors.applyColormapToData(self.ctrPoints,
+ name=self.colormap['name'],
+ normalization='linear',
+ autoscale=self.colormap['autoscale'],
+ vmin=vmin,
+ vmax=vmax)
+
+ def paintEvent(self, event):
+ """"""
+ qt.QWidget.paintEvent(self, event)
+ if self.colormap is None:
+ return
+
+ vmin = self.colormap['vmin']
+ vmax = self.colormap['vmax']
+
+ painter = qt.QPainter(self)
+ gradient = qt.QLinearGradient(0, 0, 0, self.rect().height() - 2*self.margin)
+ for iPt, pt in enumerate(self.ctrPoints):
+ colormapPosition = 1 - (pt-vmin) / (vmax-vmin)
+ assert(colormapPosition >= 0.0)
+ assert(colormapPosition <= 1.0)
+ gradient.setColorAt(colormapPosition, qt.QColor(*(self.colorsCtrPts[iPt])))
+
+ painter.setBrush(gradient)
+ painter.drawRect(
+ qt.QRect(0, self.margin, self.width(), self.height() - 2.*self.margin))
+
+ def mouseMoveEvent(self, event):
+ """"""
+ self.setToolTip(str(self.getValueFromRelativePosition(self._getRelativePosition(event.y()))))
+ super(_ColorScale, self).mouseMoveEvent(event)
+
+ def _getRelativePosition(self, yPixel):
+ """yPixel : pixel position into _ColorScale widget reference
+ """
+ # widgets are bottom-top referencial but we display in top-bottom referential
+ return 1 - float(yPixel)/float(self.height() - 2*self.margin)
+
+ def getValueFromRelativePosition(self, value):
+ """Return the value in the colorMap from a relative position in the
+ ColorScaleBar (y)
+
+ :param value: float value in [0, 1]
+ :return: the value in [colormap['vmin'], colormap['vmax']]
+ """
+ value = max(0.0, value)
+ value = min(value, 1.0)
+ vmin = self.colormap['vmin']
+ vmax = self.colormap['vmax']
+ if self.colormap['normalization'] is 'linear':
+ return vmin + (vmax - vmin) * value
+ elif self.colormap['normalization'] is 'log':
+ rpos = (numpy.log10(vmax) - numpy.log10(vmin)) * value + numpy.log10(vmin)
+ return numpy.power(10., rpos)
+ else:
+ err = "normalization type (%s) is not managed by the _ColorScale Widget" % self.colormap['normalization']
+ raise ValueError(err)
+
+ def setMargin(self, margin):
+ """Define the margin to fit with a TickBar object.
+ This is needed since we can only paint on the viewport of the widget.
+ Didn't work with a simple setContentsMargins
+
+ :param int margin: the margin to apply on the top and bottom.
+ """
+ self.margin = margin
+
+
+class _TickBar(qt.QWidget):
+ """Bar grouping the ticks displayed
+
+ To run the following sample code, a QApplication must be initialized.
+
+ >>> bar = TickBar(1, 1000, norm='log', parent=None, displayValues=True)
+ >>> bar.show()
+
+ .. image:: img/tickbar.png
+ :width: 40px
+ :align: center
+
+ :param int vmin: smaller value of the range of values
+ :param int vmax: higher value of the range of values
+ :param str norm: normalization type to be displayed. Valid values are
+ 'linear' and 'log'
+ :param parent: the Qt parent if any
+ :param bool displayValues: if True display the values close to the tick,
+ Otherwise only signal it by '-'
+ :param int nticks: the number of tick we want to display. Should be an
+ unsigned int ot None. If None, let the Tick bar find the optimal
+ number of ticks from the tick density.
+ :param int margin: margin to set on the top and bottom
+ """
+ _WIDTH_DISP_VAL = 45
+ """widget width when displayed with ticks labels"""
+ _WIDTH_NO_DISP_VAL = 10
+ """widget width when displayed without ticks labels"""
+ _FONT_SIZE = 10
+ """font size for ticks labels"""
+ _LINE_WIDTH = 10
+ """width of the line to mark a tick"""
+
+ DEFAULT_TICK_DENSITY = 0.015
+
+ def __init__(self, vmin, vmax, norm, parent=None, displayValues=True,
+ nticks=None, margin=5):
+ super(_TickBar, self).__init__(parent)
+ self._forcedDisplayType = None
+ self.ticksDensity = _TickBar.DEFAULT_TICK_DENSITY
+
+ self._vmin = vmin
+ self._vmax = vmax
+ # TODO : should be grouped into a global function, called by all
+ # logScale displayer to make sure we have the same behavior everywhere
+ if self._vmin < 1. or self._vmax < 1.:
+ _logger.warning(
+ 'Log colormap with bound <= 1: changing bounds.')
+ self._vmin, self._vmax = 1., 10.
+
+ self._norm = norm
+ self.displayValues = displayValues
+ self.setTicksNumber(nticks)
+ self.setMargin(margin)
+
+ self.setLayout(qt.QVBoxLayout())
+ self.setMargin(margin)
+ self.setContentsMargins(0, 0, 0, 0)
+
+ self._resetWidth()
+
+ def setTicksValuesVisible(self, val):
+ self.displayValues = val
+ self._resetWidth()
+
+ def _resetWidth(self):
+ self.width = _TickBar._WIDTH_DISP_VAL if self.displayValues else _TickBar._WIDTH_NO_DISP_VAL
+ self.setFixedWidth(self.width)
+
+ def update(self, vmin, vmax, norm):
+ self._vmin = vmin
+ self._vmax = vmax
+ self._norm = norm
+ self.computeTicks()
+ qt.QWidget.update(self)
+
+ def setMargin(self, margin):
+ """Define the margin to fit with a _ColorScale object.
+ This is needed since we can only paint on the viewport of the widget
+
+ :param int margin: the margin to apply on the top and bottom.
+ """
+ self.margin = margin
+
+ def setTicksNumber(self, nticks):
+ """Set the number of ticks to display.
+
+ :param nticks: the number of tick to be display. Should be an
+ unsigned int ot None. If None, let the :class:`_TickBar` find the
+ optimal number of ticks from the tick density.
+ """
+ self._nticks = nticks
+ self.ticks = None
+ self.computeTicks()
+ qt.QWidget.update(self)
+
+ def setTicksDensity(self, density):
+ """If you let :class:`_TickBar` deal with the number of ticks
+ (nticks=None) then you can specify a ticks density to be displayed.
+ """
+ if density < 0.0:
+ raise ValueError('Density should be a positive value')
+ self.ticksDensity = density
+
+ def computeTicks(self):
+ """This function compute ticks values labels. It is called at each
+ update and each resize event.
+ Deal only with linear and log scale.
+ """
+ nticks = self._nticks
+ if nticks is None:
+ nticks = self._getOptimalNbTicks()
+
+ if self._norm == 'log':
+ self._computeTicksLog(nticks)
+ elif self._norm == 'linear':
+ self._computeTicksLin(nticks)
+ else:
+ err = 'TickBar - Wrong normalization %s' % self._norm
+ raise ValueError(err)
+ # update the form
+ font = qt.QFont()
+ font.setPixelSize(_TickBar._FONT_SIZE)
+
+ self.form = self._getFormat(font)
+
+ def _computeTicksLog(self, nticks):
+ logMin = numpy.log10(self._vmin)
+ logMax = numpy.log10(self._vmax)
+ lowBound, highBound, spacing, self._nfrac = ticklayout.niceNumbersForLog10(logMin,
+ logMax,
+ nticks)
+ self.ticks = numpy.power(10., numpy.arange(lowBound, highBound, spacing))
+ if spacing == 1:
+ self.subTicks = ticklayout.computeLogSubTicks(ticks=self.ticks,
+ lowBound=numpy.power(10., lowBound),
+ highBound=numpy.power(10., highBound))
+ else:
+ self.subTicks = []
+
+ def resizeEvent(self, event):
+ qt.QWidget.resizeEvent(self, event)
+ self.computeTicks()
+
+ def _computeTicksLin(self, nticks):
+ _min, _max, _spacing, self._nfrac = ticklayout.niceNumbers(self._vmin,
+ self._vmax,
+ nticks)
+
+ self.ticks = numpy.arange(_min, _max, _spacing)
+ self.subTicks = []
+
+ def _getOptimalNbTicks(self):
+ return max(2, int(round(self.ticksDensity * self.rect().height())))
+
+ def paintEvent(self, event):
+ painter = qt.QPainter(self)
+ font = painter.font()
+ font.setPixelSize(_TickBar._FONT_SIZE)
+ painter.setFont(font)
+
+ # paint ticks
+ if self.ticks is not None:
+ for val in self.ticks:
+ self._paintTick(val, painter, majorTick=True)
+
+ # paint subticks
+ for val in self.subTicks:
+ self._paintTick(val, painter, majorTick=False)
+
+ qt.QWidget.paintEvent(self, event)
+
+ def _getRelativePosition(self, val):
+ """Return the relative position of val according to min and max value
+ """
+ if self._norm == 'linear':
+ return 1 - (val - self._vmin) / (self._vmax - self._vmin)
+ elif self._norm == 'log':
+ return 1 - (numpy.log10(val) - numpy.log10(self._vmin))/(numpy.log10(self._vmax) - numpy.log(self._vmin))
+ else:
+ raise ValueError('Norm is not recognized')
+
+ def _paintTick(self, val, painter, majorTick=True):
+ """
+
+ :param bool majorTick: if False will never draw text and will set a line
+ with a smaller width
+ """
+ fm = qt.QFontMetrics(painter.font())
+ viewportHeight = self.rect().height() - self.margin * 2
+ relativePos = self._getRelativePosition(val)
+ height = viewportHeight * relativePos
+ height += self.margin
+ lineWidth = _TickBar._LINE_WIDTH
+ if majorTick is False:
+ lineWidth /= 2
+
+ painter.drawLine(qt.QLine(self.width - lineWidth,
+ height,
+ self.width,
+ height))
+
+ if self.displayValues and majorTick is True:
+ painter.drawText(qt.QPoint(0.0, height + (fm.height() / 2)),
+ self.form.format(val))
+
+ def setDisplayType(self, disType):
+ """Set the type of display we want to set for ticks labels
+
+ :param str disType: The type of display we want to set. disType values
+ can be :
+
+ - 'std' for standard, meaning only a formatting on the number of
+ digits is done
+ - 'e' for scientific display
+ - None to let the _TickBar guess the best display for this kind of data.
+ """
+ if disType not in (None, 'std', 'e'):
+ raise ValueError("display type not recognized, value should be in (None, 'std', 'e'")
+ self._forcedDisplayType = disType
+
+ def _getStandardFormat(self):
+ return "{0:.%sf}" % self._nfrac
+
+ def _getFormat(self, font):
+ if self._forcedDisplayType is None:
+ return self._guessType(font)
+ elif self._forcedDisplayType is 'std':
+ return self._getStandardFormat()
+ elif self._forcedDisplayType is 'e':
+ return self._getScientificForm()
+ else:
+ err = 'Forced type for display %s is not recognized' % self._forcedDisplayType
+ raise ValueError(err)
+
+ def _getScientificForm(self):
+ return "{0:.0e}"
+
+ def _guessType(self, font):
+ """Try fo find the better format to display the tick's labels
+
+ :param QFont font: the font we want want to use durint the painting
+ """
+ assert(type(self._vmin) == type(self._vmax))
+ form = self._getStandardFormat()
+
+ fm = qt.QFontMetrics(font)
+ width = 0
+ for tick in self.ticks:
+ width = max(fm.width(form.format(tick)), width)
+
+ # if the length of the string are too long we are mooving to scientific
+ # display
+ if width > _TickBar._WIDTH_DISP_VAL - _TickBar._LINE_WIDTH:
+ return self._getScientificForm()
+ else:
+ return form