diff options
Diffstat (limited to 'silx/gui/plot/ColorBar.py')
-rw-r--r-- | silx/gui/plot/ColorBar.py | 864 |
1 files changed, 864 insertions, 0 deletions
diff --git a/silx/gui/plot/ColorBar.py b/silx/gui/plot/ColorBar.py new file mode 100644 index 0000000..8f4bde2 --- /dev/null +++ b/silx/gui/plot/ColorBar.py @@ -0,0 +1,864 @@ +# 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 .. import qt, icons +from silx.gui.plot import Colormap + +_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 colorbar + """ + + def __init__(self, parent=None, plot=None, legend=None): + self._isConnected = False + self._plot = None + self._viewAction = None + self._colormap = None + self._data = None + + super(ColorBarWidget, self).__init__(parent) + + 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) + + 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. + """ + self._disconnectPlot() + self._plot = plot + self._connectPlot() + + def _disconnectPlot(self): + """Disconnect from Plot signals""" + if self._plot is not None and self._isConnected: + self._isConnected = False + self._plot.sigActiveImageChanged.disconnect( + self._activeImageChanged) + self._plot.sigPlotSignal.disconnect(self._defaultColormapChanged) + + def _connectPlot(self): + """Connect to Plot signals""" + if self._plot is not None and not self._isConnected: + activeImageLegend = self._plot.getActiveImage(just_legend=True) + if activeImageLegend is None: # Show plot default colormap + self._syncWithDefaultColormap() + else: # Show active image colormap + self._activeImageChanged(None, activeImageLegend) + self._plot.sigActiveImageChanged.connect(self._activeImageChanged) + self._plot.sigPlotSignal.connect(self._defaultColormapChanged) + self._isConnected = True + + def showEvent(self, event): + self._connectPlot() + if self._viewAction is not None: + self._viewAction.setChecked(True) + + def hideEvent(self, event): + self._disconnectPlot() + if self._viewAction is not None: + self._viewAction.setChecked(False) + + def getColormap(self): + """ + + :return: the :class:`.Colormap` colormap displayed in the colorbar. + + """ + return self.getColorScaleBar().getColormap() + + def setColormap(self, colormap, data=None): + """Set the colormap to be displayed. + + :param colormap: The colormap to apply on the + ColorBarWidget + :type colormap: :class:`.Colormap` + :param numpy.ndarray data: the data to display, needed if the colormap + require an autoscale + """ + self._data = data + self.getColorScaleBar().setColormap(colormap=colormap, + data=data) + if self._colormap is not None: + self._colormap.sigChanged.disconnect(self._colormapHasChanged) + self._colormap = colormap + if self._colormap is not None: + self._colormap.sigChanged.connect(self._colormapHasChanged) + + def _colormapHasChanged(self): + """handler of the Colormap.sigChanged signal + """ + assert self._colormap is not None + self.setColormap(colormap=self._colormap, + data=self._data) + + 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, previous, legend): + """Handle plot active curve changed""" + if legend is None: # No active image, display no colormap + self.setColormap(colormap=None) + return + + # Sync with active image + image = self._plot.getActiveImage().getData(copy=False) + + # RGB(A) image, display default colormap + if image.ndim != 2: + self.setColormap(colormap=None) + 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 + self.setColormap(colormap=self._plot.getActiveImage().getColormap(), + data=image) + + def _defaultColormapChanged(self, event): + """Handle plot default colormap changed""" + if (event['event'] == 'defaultColormapChanged' and + self._plot.getActiveImage() is None): + # No active image, take default colormap update into account + self._syncWithDefaultColormap() + + def _syncWithDefaultColormap(self, data=None): + """Update colorbar according to plot default colormap""" + self.setColormap(self._plot.getDefaultColormap(), data) + + def getColorScaleBar(self): + """ + + :return: return the :class:`ColorScaleBar` used to display ColorScale + and ticks""" + return self._colorScale + + def getToggleViewAction(self): + """Returns a checkable action controlling this widget's visibility. + + :rtype: QAction + """ + if self._viewAction is None: + self._viewAction = qt.QAction(self) + self._viewAction.setText('Colorbar') + self._viewAction.setIcon(icons.getQIcon('colorbar')) + self._viewAction.setToolTip('Show/Hide the colorbar') + self._viewAction.setCheckable(True) + self._viewAction.setChecked(self.isVisible()) + self._viewAction.toggled[bool].connect(self.setVisible) + return self._viewAction + + +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 = Colormap(name='gray', + ... norm='log', + ... vmin=1, + ... vmax=100000, + ... ) + >>> 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""" + + def __init__(self, parent=None, colormap=None, data=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, + data=data, + parent=self, + margin=ColorScaleBar._TEXT_MARGIN) + if colormap: + vmin, vmax = colormap.getColormapRange(data) + else: + vmin, vmax = Colormap.DEFAULT_MIN_LIN, Colormap.DEFAULT_MAX_LIN + + norm = colormap.getNormalization() if colormap else Colormap.Colormap.LINEAR + self.tickbar = _TickBar(vmin=vmin, + vmax=vmax, + norm=norm, + parent=self, + displayValues=displayTicksValues, + margin=ColorScaleBar._TEXT_MARGIN) + + self.layout().addWidget(self.tickbar, 1, 0, 1, 1, qt.Qt.AlignRight) + self.layout().addWidget(self.colorScale, 1, 1, qt.Qt.AlignLeft) + + self.layout().setContentsMargins(0, 0, 0, 0) + self.layout().setSpacing(0) + + # max label + self._maxLabel = qt.QLabel(str(1.0), parent=self) + self._maxLabel.setToolTip(str(0.0)) + self.layout().addWidget(self._maxLabel, 0, 0, 1, 2, qt.Qt.AlignRight) + + # min label + self._minLabel = qt.QLabel(str(0.0), parent=self) + self._minLabel.setToolTip(str(0.0)) + self.layout().addWidget(self._minLabel, 2, 0, 1, 2, qt.Qt.AlignRight) + + self.layout().setSizeConstraint(qt.QLayout.SetMinAndMaxSize) + self.layout().setColumnStretch(0, 1) + self.layout().setRowStretch(1, 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 getColormap(self): + """ + + :returns: the colormap. + :rtype: :class:`.Colormap` + """ + return self.colorScale.getColormap() + + def setColormap(self, colormap, data=None): + """Set the new colormap to be displayed + + :param Colormap colormap: the colormap to set + :param numpy.ndarray data: the data to display, needed if the colormap + require an autoscale + """ + self.colorScale.setColormap(colormap, data) + + if colormap is not None: + vmin, vmax = colormap.getColormapRange(data) + norm = colormap.getNormalization() + else: + vmin, vmax = None, None + norm = None + + self.tickbar.update(vmin=vmin, + vmax=vmax, + norm=norm) + self._setMinMaxLabels(vmin, 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.minVal is None: + text, tooltip = '', '' + else: + if self.minVal == 0 or 0 <= numpy.log10(abs(self.minVal)) < 7: + text = '%.7g' % self.minVal + else: + text = '%.2e' % self.minVal + tooltip = repr(self.minVal) + + self._minLabel.setText(text) + self._minLabel.setToolTip(tooltip) + + if self.maxVal is None: + text, tooltip = '', '' + else: + if self.maxVal == 0 or 0 <= numpy.log10(abs(self.maxVal)) < 7: + text = '%.7g' % self.maxVal + else: + text = '%.2e' % self.maxVal + tooltip = repr(self.maxVal) + + self._maxLabel.setText(text) + self._maxLabel.setToolTip(tooltip) + + 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 = Colormap(name='viridis', + ... norm='log', + ... vmin=1, + ... vmax=100000, + ... ) + >>> 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, data=None): + qt.QWidget.__init__(self, parent) + self._colormap = None + self.margin = margin + self.setColormap(colormap, data) + + self.setLayout(qt.QVBoxLayout()) + self.setSizePolicy(qt.QSizePolicy.Fixed, 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) + + self.setMinimumHeight(self._NB_CONTROL_POINTS // 2 + 2 * self.margin) + self.setFixedWidth(25) + + def setColormap(self, colormap, data=None): + """Set the new colormap to be displayed + + :param dict colormap: the colormap to set + :param data: Optional data for which to compute colormap range. + """ + self._colormap = colormap + self.setEnabled(colormap is not None) + + if colormap is None: + self.vmin, self.vmax = None, None + else: + assert colormap.getNormalization() in Colormap.Colormap.NORMALIZATIONS + self.vmin, self.vmax = self._colormap.getColormapRange(data=data) + self._updateColorGradient() + self.update() + + def getColormap(self): + """Returns the colormap + + :rtype: :class:`.Colormap` + """ + return None if self._colormap is None else self._colormap + + def _updateColorGradient(self): + """Compute the color gradient""" + colormap = self.getColormap() + if colormap is None: + return + + indices = numpy.linspace(0., 1., self._NB_CONTROL_POINTS) + colormapDisp = Colormap.Colormap(name=colormap.getName(), + normalization=Colormap.Colormap.LINEAR, + vmin=None, + vmax=None, + colors=colormap.getColormapLUT()) + colors = colormapDisp.applyToData(indices) + self._gradient = qt.QLinearGradient(0, 1, 0, 0) + self._gradient.setCoordinateMode(qt.QGradient.StretchToDeviceMode) + self._gradient.setStops( + [(i, qt.QColor(*color)) for i, color in zip(indices, colors)] + ) + + def paintEvent(self, event): + """""" + painter = qt.QPainter(self) + if self.getColormap() is not None: + painter.setBrush(self._gradient) + penColor = self.palette().color(qt.QPalette.Active, + qt.QPalette.Foreground) + else: + penColor = self.palette().color(qt.QPalette.Disabled, + qt.QPalette.Foreground) + painter.setPen(penColor) + + painter.drawRect(qt.QRect( + 0, + self.margin, + self.width() - 1., + self.height() - 2. * self.margin - 1.)) + + def mouseMoveEvent(self, event): + tooltip = str(self.getValueFromRelativePosition( + self._getRelativePosition(event.y()))) + qt.QToolTip.showText(event.globalPos(), tooltip, self) + 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. - (yPixel - self.margin) / 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']] + """ + colormap = self.getColormap() + if colormap is None: + return + + value = max(0.0, value) + value = min(value, 1.0) + + vmin = self.vmin + vmax = self.vmax + if colormap.getNormalization() == Colormap.Colormap.LINEAR: + return vmin + (vmax - vmin) * value + elif colormap.getNormalization() == Colormap.Colormap.LOGARITHM: + 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" % 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 + self.update() + + +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.margin = margin + self._nticks = None + self.ticks = () + self.subTicks = () + self._forcedDisplayType = None + self.ticksDensity = _TickBar.DEFAULT_TICK_DENSITY + + self._vmin = vmin + self._vmax = vmax + self._norm = norm + self.displayValues = displayValues + self.setTicksNumber(nticks) + + self.setMargin(margin) + self.setContentsMargins(0, 0, 0, 0) + + self._resetWidth() + + def setTicksValuesVisible(self, val): + self.displayValues = val + self._resetWidth() + + def _resetWidth(self): + width = self._WIDTH_DISP_VAL if self.displayValues else self._WIDTH_NO_DISP_VAL + self.setFixedWidth(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.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._vmin == self._vmax: + # No range: no ticks + self.ticks = () + self.subTicks = () + elif self._norm == Colormap.Colormap.LOGARITHM: + self._computeTicksLog(nticks) + elif self._norm == Colormap.Colormap.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 + for val in self.ticks: + self._paintTick(val, painter, majorTick=True) + + # paint subticks + for val in self.subTicks: + self._paintTick(val, painter, majorTick=False) + + def _getRelativePosition(self, val): + """Return the relative position of val according to min and max value + """ + if self._norm == Colormap.Colormap.LINEAR: + return 1 - (val - self._vmin) / (self._vmax - self._vmin) + elif self._norm == Colormap.Colormap.LOGARITHM: + 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 - 1 + 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 + """ + 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 |