Source code for qwt.plot_layout

# -*- 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)

"""
QwtPlotLayout
-------------

.. autoclass:: QwtPlotLayout
   :members:
"""

import math

from qtpy.QtCore import QRectF, QSize, Qt
from qtpy.QtGui import QFont, QRegion

from qwt.plot import QwtPlot
from qwt.scale_draw import QwtAbstractScaleDraw
from qwt.scale_widget import QwtScaleWidget
from qwt.text import QwtText

QWIDGETSIZE_MAX = (1 << 24) - 1


class LegendData(object):
    def __init__(self):
        self.frameWidth = None
        self.hScrollExtent = None
        self.vScrollExtent = None
        self.hint = QSize()


class TitleData(object):
    def __init__(self):
        self.text = QwtText()
        self.frameWidth = None


class FooterData(object):
    def __init__(self):
        self.text = QwtText()
        self.frameWidth = None


class ScaleData(object):
    def __init__(self):
        self.isEnabled = None
        self.scaleWidget = QwtScaleWidget()
        self.scaleFont = QFont()
        self.start = None
        self.end = None
        self.baseLineOffset = None
        self.tickOffset = None
        self.dimWithoutTitle = None


class CanvasData(object):
    def __init__(self):
        self.contentsMargins = [0 for _i in QwtPlot.AXES]


class QwtPlotLayout_LayoutData(object):
    def __init__(self):
        self.legend = LegendData()
        self.title = TitleData()
        self.footer = FooterData()
        self.scale = [ScaleData() for _i in QwtPlot.AXES]
        self.canvas = CanvasData()

    def init(self, plot, rect):
        """Extract all layout relevant data from the plot components"""
        # legend
        legend = plot.legend()
        if legend:
            self.legend.frameWidth = legend.frameWidth()
            self.legend.hScrollExtent = legend.scrollExtent(Qt.Horizontal)
            self.legend.vScrollExtent = legend.scrollExtent(Qt.Vertical)
            hint = legend.sizeHint()
            w = min([hint.width(), math.floor(rect.width())])
            h = legend.heightForWidth(w)
            if h <= 0:
                h = hint.height()
            self.legend.hint = QSize(w, h)
        # title
        self.title.frameWidth = 0
        self.title.text = QwtText()
        if plot.titleLabel():
            label = plot.titleLabel()
            self.title.text = label.text()
            if not self.title.text.testPaintAttribute(QwtText.PaintUsingTextFont):
                self.title.text.setFont(label.font())
            self.title.frameWidth = plot.titleLabel().frameWidth()
        # footer
        self.footer.frameWidth = 0
        self.footer.text = QwtText()
        if plot.footerLabel():
            label = plot.footerLabel()
            self.footer.text = label.text()
            if not self.footer.text.testPaintAttribute(QwtText.PaintUsingTextFont):
                self.footer.text.setFont(label.font())
            self.footer.frameWidth = plot.footerLabel().frameWidth()
        # scales
        for axis in QwtPlot.AXES:
            if plot.axisEnabled(axis):
                scaleWidget = plot.axisWidget(axis)
                self.scale[axis].isEnabled = True
                self.scale[axis].scaleWidget = scaleWidget
                self.scale[axis].scaleFont = scaleWidget.font()
                self.scale[axis].start = scaleWidget.startBorderDist()
                self.scale[axis].end = scaleWidget.endBorderDist()
                self.scale[axis].baseLineOffset = scaleWidget.margin()
                self.scale[axis].tickOffset = scaleWidget.margin()
                if scaleWidget.scaleDraw().hasComponent(QwtAbstractScaleDraw.Ticks):
                    self.scale[
                        axis
                    ].tickOffset += scaleWidget.scaleDraw().maxTickLength()
                self.scale[axis].dimWithoutTitle = scaleWidget.dimForLength(
                    QWIDGETSIZE_MAX, self.scale[axis].scaleFont
                )
                if not scaleWidget.title().isEmpty():
                    self.scale[axis].dimWithoutTitle -= scaleWidget.titleHeightForWidth(
                        QWIDGETSIZE_MAX
                    )
            else:
                self.scale[axis].isEnabled = False
                self.scale[axis].start = 0
                self.scale[axis].end = 0
                self.scale[axis].baseLineOffset = 0
                self.scale[axis].tickOffset = 0.0
                self.scale[axis].dimWithoutTitle = 0
        layout = plot.canvas().layout()
        if layout is not None:
            mgn = layout.contentsMargins()
            self.canvas.contentsMargins = [
                mgn.left(),
                mgn.top(),
                mgn.right(),
                mgn.bottom(),
            ]


class QwtPlotLayout_PrivateData(object):
    def __init__(self):
        self.spacing = 5
        self.titleRect = QRectF()
        self.footerRect = QRectF()
        self.legendRect = QRectF()
        self.scaleRect = [QRectF() for _i in QwtPlot.AXES]
        self.canvasRect = QRectF()
        self.layoutData = QwtPlotLayout_LayoutData()
        self.legendPos = None
        self.legendRatio = None
        self.canvasMargin = [0] * len(QwtPlot.AXES)
        self.alignCanvasToScales = [False] * len(QwtPlot.AXES)


[docs] class QwtPlotLayout(object): """ Layout engine for QwtPlot. It is used by the `QwtPlot` widget to organize its internal widgets or by `QwtPlot.print()` to render its content to a QPaintDevice like a QPrinter, QPixmap/QImage or QSvgRenderer. .. seealso:: :py:meth:`qwt.plot.QwtPlot.setPlotLayout()` Valid options: * `QwtPlotLayout.AlignScales`: Unused * `QwtPlotLayout.IgnoreScrollbars`: Ignore the dimension of the scrollbars. There are no scrollbars, when the plot is not rendered to widgets. * `QwtPlotLayout.IgnoreFrames`: Ignore all frames. * `QwtPlotLayout.IgnoreLegend`: Ignore the legend. * `QwtPlotLayout.IgnoreTitle`: Ignore the title. * `QwtPlotLayout.IgnoreFooter`: Ignore the footer. """ # enum Option AlignScales = 0x01 IgnoreScrollbars = 0x02 IgnoreFrames = 0x04 IgnoreLegend = 0x08 IgnoreTitle = 0x10 IgnoreFooter = 0x20 def __init__(self): self.__data = QwtPlotLayout_PrivateData() self.setLegendPosition(QwtPlot.BottomLegend) self.setCanvasMargin(4) self.setAlignCanvasToScales(False) self.invalidate()
[docs] def setCanvasMargin(self, margin, axis=-1): """ Change a margin of the canvas. The margin is the space above/below the scale ticks. A negative margin will be set to -1, excluding the borders of the scales. :param int margin: New margin :param int axisId: Axis index .. seealso:: :py:meth:`canvasMargin()` .. warning:: The margin will have no effect when `alignCanvasToScale()` is True """ if margin < 1: margin = -1 if axis == -1: for axis in QwtPlot.AXES: self.__data.canvasMargin[axis] = margin elif axis in QwtPlot.AXES: self.__data.canvasMargin[axis] = margin
[docs] def canvasMargin(self, axisId): """ :param int axisId: Axis index :return: Margin around the scale tick borders .. seealso:: :py:meth:`setCanvasMargin()` """ if axisId not in QwtPlot.AXES: return 0 return self.__data.canvasMargin[axisId]
[docs] def setAlignCanvasToScales(self, *args): """ Change the align-canvas-to-axis-scales setting. .. py:method:: setAlignCanvasToScales(on): Set the align-canvas-to-axis-scales flag for all axes :param bool on: True/False .. py:method:: setAlignCanvasToScales(axisId, on): Change the align-canvas-to-axis-scales setting. The canvas may: - extend beyond the axis scale ends to maximize its size, - align with the axis scale ends to control its size. The axisId parameter is somehow confusing as it identifies a border of the plot and not the axes, that are aligned. F.e when `QwtPlot.yLeft` is set, the left end of the the x-axes (`QwtPlot.xTop`, `QwtPlot.xBottom`) is aligned. :param int axisId: Axis index :param bool on: True/False .. seealso:: :py:meth:`setAlignCanvasToScale()`, :py:meth:`alignCanvasToScale()` """ if len(args) == 1: (on,) = args for axis in QwtPlot.AXES: self.__data.alignCanvasToScales[axis] = on elif len(args) == 2: axisId, on = args if axisId in QwtPlot.AXES: self.__data.alignCanvasToScales[axisId] = on else: raise TypeError( "%s().setAlignCanvasToScales() takes 1 or 2 " "argument(s) (%s given)" % (self.__class__.__name__, len(args)) )
[docs] def alignCanvasToScale(self, axisId): """ Return the align-canvas-to-axis-scales setting. The canvas may: - extend beyond the axis scale ends to maximize its size - align with the axis scale ends to control its size. :param int axisId: Axis index :return: align-canvas-to-axis-scales setting .. seealso:: :py:meth:`setAlignCanvasToScale()`, :py:meth:`setCanvasMargin()` """ if axisId not in QwtPlot.AXES: return False return self.__data.alignCanvasToScales[axisId]
[docs] def setSpacing(self, spacing): """ Change the spacing of the plot. The spacing is the distance between the plot components. :param int spacing: New spacing .. seealso:: :py:meth:`setCanvasMargin()`, :py:meth:`spacing()` """ self.__data.spacing = max([0, spacing])
[docs] def spacing(self): """ :return: Spacing .. seealso:: :py:meth:`margin()`, :py:meth:`setSpacing()` """ return self.__data.spacing
[docs] def setLegendPosition(self, *args): """ Specify the position of the legend .. py:method:: setLegendPosition(pos, [ratio=0.]): Specify the position of the legend :param QwtPlot.LegendPosition pos: Legend position :param float ratio: Ratio between legend and the bounding rectangle of title, footer, canvas and axes The legend will be shrunk if it would need more space than the given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 it will be reset to the default ratio. The default vertical/horizontal ratio is 0.33/0.5. Valid position values: * `QwtPlot.LeftLegend`, * `QwtPlot.RightLegend`, * `QwtPlot.TopLegend`, * `QwtPlot.BottomLegend` .. seealso:: :py:meth:`setLegendPosition()` """ if len(args) == 2: pos, ratio = args if ratio > 1.0: ratio = 1.0 if pos in (QwtPlot.TopLegend, QwtPlot.BottomLegend): if ratio <= 0.0: ratio = 0.33 self.__data.legendRatio = ratio self.__data.legendPos = pos elif pos in (QwtPlot.LeftLegend, QwtPlot.RightLegend): if ratio <= 0.0: ratio = 0.5 self.__data.legendRatio = ratio self.__data.legendPos = pos elif len(args) == 1: (pos,) = args self.setLegendPosition(pos, 0.0) else: raise TypeError( "%s().setLegendPosition() takes 1 or 2 argument(s)" "(%s given)" % (self.__class__.__name__, len(args)) )
[docs] def legendPosition(self): """ :return: Position of the legend .. seealso:: :py:meth:`legendPosition()` """ return self.__data.legendPos
[docs] def setLegendRatio(self, ratio): """ Specify the relative size of the legend in the plot :param float ratio: Ratio between legend and the bounding rectangle of title, footer, canvas and axes The legend will be shrunk if it would need more space than the given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 it will be reset to the default ratio. The default vertical/horizontal ratio is 0.33/0.5. .. seealso:: :py:meth:`legendRatio()` """ self.setLegendPosition(self.legendPosition(), ratio)
[docs] def legendRatio(self): """ :return: The relative size of the legend in the plot. .. seealso:: :py:meth:`setLegendRatio()` """ return self.__data.legendRatio
[docs] def setTitleRect(self, rect): """ Set the geometry for the title This method is intended to be used from derived layouts overloading `activate()` :param QRectF rect: Rectangle .. seealso:: :py:meth:`titleRect()`, :py:meth:`activate()` """ self.__data.titleRect = rect
[docs] def titleRect(self): """ :return: Geometry for the title .. seealso:: :py:meth:`invalidate()`, :py:meth:`activate()` """ return self.__data.titleRect
[docs] def setFooterRect(self, rect): """ Set the geometry for the footer This method is intended to be used from derived layouts overloading `activate()` :param QRectF rect: Rectangle .. seealso:: :py:meth:`footerRect()`, :py:meth:`activate()` """ self.__data.footerRect = rect
[docs] def footerRect(self): """ :return: Geometry for the footer .. seealso:: :py:meth:`invalidate()`, :py:meth:`activate()` """ return self.__data.footerRect
[docs] def setLegendRect(self, rect): """ Set the geometry for the legend This method is intended to be used from derived layouts overloading `activate()` :param QRectF rect: Rectangle for the legend .. seealso:: :py:meth:`footerRect()`, :py:meth:`activate()` """ self.__data.legendRect = rect
[docs] def legendRect(self): """ :return: Geometry for the legend .. seealso:: :py:meth:`invalidate()`, :py:meth:`activate()` """ return self.__data.legendRect
[docs] def setScaleRect(self, axis, rect): """ Set the geometry for an axis This method is intended to be used from derived layouts overloading `activate()` :param int axisId: Axis index :param QRectF rect: Rectangle for the scale .. seealso:: :py:meth:`scaleRect()`, :py:meth:`activate()` """ if axis in QwtPlot.AXES: self.__data.scaleRect[axis] = rect
[docs] def scaleRect(self, axis): """ :param int axisId: Axis index :return: Geometry for the scale .. seealso:: :py:meth:`invalidate()`, :py:meth:`activate()` """ if axis not in QwtPlot.AXES: return QRectF() return self.__data.scaleRect[axis]
[docs] def setCanvasRect(self, rect): """ Set the geometry for the canvas This method is intended to be used from derived layouts overloading `activate()` :param QRectF rect: Rectangle .. seealso:: :py:meth:`canvasRect()`, :py:meth:`activate()` """ self.__data.canvasRect = rect
[docs] def canvasRect(self): """ :return: Geometry for the canvas .. seealso:: :py:meth:`invalidate()`, :py:meth:`activate()` """ return self.__data.canvasRect
[docs] def invalidate(self): """ Invalidate the geometry of all components. .. seealso:: :py:meth:`activate()` """ self.__data.titleRect = QRectF() self.__data.footerRect = QRectF() self.__data.legendRect = QRectF() self.__data.canvasRect = QRectF() for axis in QwtPlot.AXES: self.__data.scaleRect[axis] = QRectF()
[docs] def minimumSizeHint(self, plot): """ :param qwt.plot.QwtPlot plot: Plot widget :return: Minimum size hint .. seealso:: :py:meth:`qwt.plot.QwtPlot.minimumSizeHint()` """ class _ScaleData(object): def __init__(self): self.w = 0 self.h = 0 self.minLeft = 0 self.minRight = 0 self.tickOffset = 0 scaleData = [_ScaleData() for _i in QwtPlot.AXES] canvasBorder = [0 for _i in QwtPlot.AXES] layout = plot.canvas().layout() if layout is None: left, top, right, bottom = 0, 0, 0, 0 else: mgn = layout.contentsMargins() left, top, right, bottom = ( mgn.left(), mgn.top(), mgn.right(), mgn.bottom(), ) for axis in QwtPlot.AXES: if plot.axisEnabled(axis): scl = plot.axisWidget(axis) sd = scaleData[axis] hint = scl.minimumSizeHint() sd.w = hint.width() sd.h = hint.height() sd.minLeft, sd.minLeft = scl.getBorderDistHint() sd.tickOffset = scl.margin() if scl.scaleDraw().hasComponent(QwtAbstractScaleDraw.Ticks): sd.tickOffset += math.ceil(scl.scaleDraw().maxTickLength()) canvasBorder[axis] = left + self.__data.canvasMargin[axis] + 1 for axis in QwtPlot.AXES: sd = scaleData[axis] if sd.w and axis in (QwtPlot.xBottom, QwtPlot.xTop): if ( sd.minLeft > canvasBorder[QwtPlot.yLeft] and scaleData[QwtPlot.yLeft].w ): shiftLeft = sd.minLeft - canvasBorder[QwtPlot.yLeft] if shiftLeft > scaleData[QwtPlot.yLeft].w: shiftLeft = scaleData[QwtPlot.yLeft].w sd.w -= shiftLeft if ( sd.minRight > canvasBorder[QwtPlot.yRight] and scaleData[QwtPlot.yRight].w ): shiftRight = sd.minRight - canvasBorder[QwtPlot.yRight] if shiftRight > scaleData[QwtPlot.yRight].w: shiftRight = scaleData[QwtPlot.yRight].w sd.w -= shiftRight if sd.h and axis in (QwtPlot.yLeft, QwtPlot.yRight): if ( sd.minLeft > canvasBorder[QwtPlot.xBottom] and scaleData[QwtPlot.xBottom].h ): shiftBottom = sd.minLeft - canvasBorder[QwtPlot.xBottom] if shiftBottom > scaleData[QwtPlot.xBottom].tickOffset: shiftBottom = scaleData[QwtPlot.xBottom].tickOffset sd.h -= shiftBottom if ( sd.minLeft > canvasBorder[QwtPlot.xTop] and scaleData[QwtPlot.xTop].h ): shiftTop = sd.minRight - canvasBorder[QwtPlot.xTop] if shiftTop > scaleData[QwtPlot.xTop].tickOffset: shiftTop = scaleData[QwtPlot.xTop].tickOffset sd.h -= shiftTop canvas = plot.canvas() minCanvasSize = canvas.minimumSize() w = scaleData[QwtPlot.yLeft].w + scaleData[QwtPlot.yRight].w cw = ( max([scaleData[QwtPlot.xBottom].w, scaleData[QwtPlot.xTop].w]) + left + 1 + right + 1 ) w += max([cw, minCanvasSize.width()]) h = scaleData[QwtPlot.xBottom].h + scaleData[QwtPlot.xTop].h ch = ( max([scaleData[QwtPlot.yLeft].h, scaleData[QwtPlot.yRight].h]) + top + 1 + bottom + 1 ) h += max([ch, minCanvasSize.height()]) for label in [plot.titleLabel(), plot.footerLabel()]: if label and not label.text().isEmpty(): centerOnCanvas = not plot.axisEnabled( QwtPlot.yLeft ) and plot.axisEnabled(QwtPlot.yRight) labelW = w if centerOnCanvas: labelW -= scaleData[QwtPlot.yLeft].w + scaleData[QwtPlot.yRight].w labelH = label.heightForWidth(labelW) if labelH > labelW: w = labelW = labelH if centerOnCanvas: w += scaleData[QwtPlot.yLeft].w + scaleData[QwtPlot.yRight].w labelH = label.heightForWidth(labelW) h += labelH + self.__data.spacing legend = plot.legend() if legend and not legend.isEmpty(): if self.__data.legendPos in (QwtPlot.LeftLegend, QwtPlot.RightLegend): legendW = legend.sizeHint().width() legendH = legend.heightForWidth(legendW) if legend.frameWidth() > 0: w += self.__data.spacing if legendH > h: legendW += legend.scrollExtent(Qt.Horizontal) if self.__data.legendRatio < 1.0: legendW = min([legendW, int(w / (1.0 - self.__data.legendRatio))]) w += legendW + self.__data.spacing else: legendW = min([legend.sizeHint().width(), w]) legendH = legend.heightForWidth(legendW) if legend.frameWidth() > 0: h += self.__data.spacing if self.__data.legendRatio < 1.0: legendH = min([legendH, int(h / (1.0 - self.__data.legendRatio))]) h += legendH + self.__data.spacing return QSize(int(w), int(h))
[docs] def layoutLegend(self, options, rect): """ Find the geometry for the legend :param options: Options how to layout the legend :param QRectF rect: Rectangle where to place the legend :return: Geometry for the legend """ hint = self.__data.layoutData.legend.hint if self.__data.legendPos in (QwtPlot.LeftLegend, QwtPlot.RightLegend): dim = min([hint.width(), int(rect.width() * self.__data.legendRatio)]) if not (options & self.IgnoreScrollbars): if hint.height() > rect.height(): dim += self.__data.layoutData.legend.hScrollExtent else: dim = min([hint.height(), int(rect.height() * self.__data.legendRatio)]) dim = max([dim, self.__data.layoutData.legend.vScrollExtent]) legendRect = QRectF(rect) if self.__data.legendPos == QwtPlot.LeftLegend: legendRect.setWidth(dim) elif self.__data.legendPos == QwtPlot.RightLegend: legendRect.setX(rect.right() - dim) legendRect.setWidth(dim) elif self.__data.legendPos == QwtPlot.TopLegend: legendRect.setHeight(dim) elif self.__data.legendPos == QwtPlot.BottomLegend: legendRect.setY(rect.bottom() - dim) legendRect.setHeight(dim) return legendRect
[docs] def alignLegend(self, canvasRect, legendRect): """ Align the legend to the canvas :param QRectF canvasRect: Geometry of the canvas :param QRectF legendRect: Maximum geometry for the legend :return: Geometry for the aligned legend """ alignedRect = legendRect if self.__data.legendPos in (QwtPlot.BottomLegend, QwtPlot.TopLegend): if self.__data.layoutData.legend.hint.width() < canvasRect.width(): alignedRect.setX(canvasRect.x()) alignedRect.setWidth(canvasRect.width()) else: if self.__data.layoutData.legend.hint.height() < canvasRect.height(): alignedRect.setY(canvasRect.y()) alignedRect.setHeight(canvasRect.height()) return alignedRect
[docs] def expandLineBreaks(self, options, rect): """ Expand all line breaks in text labels, and calculate the height of their widgets in orientation of the text. :param options: Options how to layout the legend :param QRectF rect: Bounding rectangle for title, footer, axes and canvas. :return: tuple `(dimTitle, dimFooter, dimAxes)` Returns: * `dimTitle`: Expanded height of the title widget * `dimFooter`: Expanded height of the footer widget * `dimAxes`: Expanded heights of the axis in axis orientation. """ dimTitle = dimFooter = 0 dimAxes = [0 for axis in QwtPlot.AXES] backboneOffset = [0 for _i in QwtPlot.AXES] for axis in QwtPlot.AXES: if not (options & self.IgnoreFrames): backboneOffset[axis] += self.__data.layoutData.canvas.contentsMargins[ axis ] if not self.__data.alignCanvasToScales[axis]: backboneOffset[axis] += self.__data.canvasMargin[axis] done = False while not done: done = True # the size for the 4 axis depend on each other. Expanding # the height of a horizontal axis will shrink the height # for the vertical axis, shrinking the height of a vertical # axis will result in a line break what will expand the # width and results in shrinking the width of a horizontal # axis what might result in a line break of a horizontal # axis ... . So we loop as long until no size changes. if not ( (options & self.IgnoreTitle) or self.__data.layoutData.title.text.isEmpty() ): w = rect.width() if ( self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled ): w -= dimAxes[QwtPlot.yLeft] + dimAxes[QwtPlot.yRight] d = math.ceil(self.__data.layoutData.title.text.heightForWidth(w)) if not (options & self.IgnoreFrames): d += 2 * self.__data.layoutData.title.frameWidth if d > dimTitle: dimTitle = d done = False if not ( (options & self.IgnoreFooter) or self.__data.layoutData.footer.text.isEmpty() ): w = rect.width() if ( self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled ): w -= dimAxes[QwtPlot.yLeft] + dimAxes[QwtPlot.yRight] d = math.ceil(self.__data.layoutData.footer.text.heightForWidth(w)) if not (options & self.IgnoreFrames): d += 2 * self.__data.layoutData.footer.frameWidth if d > dimFooter: dimFooter = d done = False for axis in QwtPlot.AXES: scaleData = self.__data.layoutData.scale[axis] if scaleData.isEnabled: if axis in (QwtPlot.xTop, QwtPlot.xBottom): length = ( rect.width() - dimAxes[QwtPlot.yLeft] - dimAxes[QwtPlot.yRight] ) length -= scaleData.start + scaleData.end if dimAxes[QwtPlot.yRight] > 0: length -= 1 length += min( [ dimAxes[QwtPlot.yLeft], scaleData.start - backboneOffset[QwtPlot.yLeft], ] ) length += min( [ dimAxes[QwtPlot.yRight], scaleData.end - backboneOffset[QwtPlot.yRight], ] ) else: length = ( rect.height() - dimAxes[QwtPlot.xTop] - dimAxes[QwtPlot.xBottom] ) length -= scaleData.start + scaleData.end length -= 1 if dimAxes[QwtPlot.xBottom] <= 0: length -= 1 if dimAxes[QwtPlot.xTop] <= 0: length -= 1 if dimAxes[QwtPlot.xBottom] > 0: length += min( [ self.__data.layoutData.scale[ QwtPlot.xBottom ].tickOffset, float( scaleData.start - backboneOffset[QwtPlot.xBottom] ), ] ) if dimAxes[QwtPlot.xTop] > 0: length += min( [ self.__data.layoutData.scale[ QwtPlot.xTop ].tickOffset, float(scaleData.end - backboneOffset[QwtPlot.xTop]), ] ) if dimTitle > 0: length -= dimTitle + self.__data.spacing d = scaleData.dimWithoutTitle if not scaleData.scaleWidget.title().isEmpty(): d += scaleData.scaleWidget.titleHeightForWidth( math.floor(length) ) if d > dimAxes[axis]: dimAxes[axis] = d done = False return dimTitle, dimFooter, dimAxes
[docs] def alignScales(self, options, canvasRect, scaleRect): """ Align the ticks of the axis to the canvas borders using the empty corners. :param options: Options how to layout the legend :param QRectF canvasRect: Geometry of the canvas ( IN/OUT ) :param QRectF scaleRect: Geometry of the scales ( IN/OUT ) """ backboneOffset = [0 for _i in QwtPlot.AXES] for axis in QwtPlot.AXES: backboneOffset[axis] = 0 if not self.__data.alignCanvasToScales[axis]: backboneOffset[axis] += self.__data.canvasMargin[axis] if not options & self.IgnoreFrames: backboneOffset[axis] += self.__data.layoutData.canvas.contentsMargins[ axis ] for axis in QwtPlot.AXES: if not scaleRect[axis].isValid(): continue startDist = self.__data.layoutData.scale[axis].start endDist = self.__data.layoutData.scale[axis].end axisRect = scaleRect[axis] if axis in (QwtPlot.xTop, QwtPlot.xBottom): leftScaleRect = scaleRect[QwtPlot.yLeft] leftOffset = backboneOffset[QwtPlot.yLeft] - startDist if leftScaleRect.isValid(): dx = leftOffset + leftScaleRect.width() if self.__data.alignCanvasToScales[QwtPlot.yLeft] and dx < 0.0: cLeft = canvasRect.left() canvasRect.setLeft(max([cLeft, axisRect.left() - dx])) else: minLeft = leftScaleRect.left() left = axisRect.left() + leftOffset axisRect.setLeft(max([left, minLeft])) else: if ( self.__data.alignCanvasToScales[QwtPlot.yLeft] and leftOffset < 0 ): canvasRect.setLeft( max([canvasRect.left(), axisRect.left() - leftOffset]) ) else: if leftOffset > 0: axisRect.setLeft(axisRect.left() + leftOffset) rightScaleRect = scaleRect[QwtPlot.yRight] rightOffset = backboneOffset[QwtPlot.yRight] - endDist + 1 if rightScaleRect.isValid(): dx = rightOffset + rightScaleRect.width() if self.__data.alignCanvasToScales[QwtPlot.yRight] and dx < 0: cRight = canvasRect.right() canvasRect.setRight(min([cRight, axisRect.right() + dx])) maxRight = rightScaleRect.right() right = axisRect.right() - rightOffset axisRect.setRight(min([right, maxRight])) else: if ( self.__data.alignCanvasToScales[QwtPlot.yRight] and rightOffset < 0 ): canvasRect.setRight( min([canvasRect.right(), axisRect.right() + rightOffset]) ) else: if rightOffset > 0: axisRect.setRight(axisRect.right() - rightOffset) else: bottomScaleRect = scaleRect[QwtPlot.xBottom] bottomOffset = backboneOffset[QwtPlot.xBottom] - endDist + 1 if bottomScaleRect.isValid(): dy = bottomOffset + bottomScaleRect.height() if self.__data.alignCanvasToScales[QwtPlot.xBottom] and dy < 0: cBottom = canvasRect.bottom() canvasRect.setBottom(min([cBottom, axisRect.bottom() + dy])) else: maxBottom = ( bottomScaleRect.top() + self.__data.layoutData.scale[QwtPlot.xBottom].tickOffset ) bottom = axisRect.bottom() - bottomOffset axisRect.setBottom(min([bottom, maxBottom])) else: if ( self.__data.alignCanvasToScales[QwtPlot.xBottom] and bottomOffset < 0 ): canvasRect.setBottom( min([canvasRect.bottom(), axisRect.bottom() + bottomOffset]) ) else: if bottomOffset > 0: axisRect.setBottom(axisRect.bottom() - bottomOffset) topScaleRect = scaleRect[QwtPlot.xTop] topOffset = backboneOffset[QwtPlot.xTop] - startDist if topScaleRect.isValid(): dy = topOffset + topScaleRect.height() if self.__data.alignCanvasToScales[QwtPlot.xTop] and dy < 0: cTop = canvasRect.top() canvasRect.setTop(max([cTop, axisRect.top() - dy])) else: minTop = ( topScaleRect.bottom() - self.__data.layoutData.scale[QwtPlot.xTop].tickOffset ) top = axisRect.top() + topOffset axisRect.setTop(max([top, minTop])) else: if self.__data.alignCanvasToScales[QwtPlot.xTop] and topOffset < 0: canvasRect.setTop( max([canvasRect.top(), axisRect.top() - topOffset]) ) else: if topOffset > 0: axisRect.setTop(axisRect.top() + topOffset) for axis in QwtPlot.AXES: sRect = scaleRect[axis] if not sRect.isValid(): continue if axis in (QwtPlot.xBottom, QwtPlot.xTop): if self.__data.alignCanvasToScales[QwtPlot.yLeft]: y = canvasRect.left() - self.__data.layoutData.scale[axis].start if not options & self.IgnoreFrames: y += self.__data.layoutData.canvas.contentsMargins[ QwtPlot.yLeft ] sRect.setLeft(y) if self.__data.alignCanvasToScales[QwtPlot.yRight]: y = canvasRect.right() - 1 + self.__data.layoutData.scale[axis].end if not options & self.IgnoreFrames: y -= self.__data.layoutData.canvas.contentsMargins[ QwtPlot.yRight ] sRect.setRight(y) if self.__data.alignCanvasToScales[axis]: if axis == QwtPlot.xTop: sRect.setBottom(canvasRect.top()) else: sRect.setTop(canvasRect.bottom()) else: if self.__data.alignCanvasToScales[QwtPlot.xTop]: x = canvasRect.top() - self.__data.layoutData.scale[axis].start if not options & self.IgnoreFrames: x += self.__data.layoutData.canvas.contentsMargins[QwtPlot.xTop] sRect.setTop(x) if self.__data.alignCanvasToScales[QwtPlot.xBottom]: x = canvasRect.bottom() - 1 + self.__data.layoutData.scale[axis].end if not options & self.IgnoreFrames: x -= self.__data.layoutData.canvas.contentsMargins[ QwtPlot.xBottom ] sRect.setBottom(x) if self.__data.alignCanvasToScales[axis]: if axis == QwtPlot.yLeft: sRect.setRight(canvasRect.left()) else: sRect.setLeft(canvasRect.right())
[docs] def activate(self, plot, plotRect, options=0x00): """ Recalculate the geometry of all components. :param qwt.plot.QwtPlot plot: Plot to be layout :param QRectF plotRect: Rectangle where to place the components :param options: Layout options """ self.invalidate() rect = QRectF(plotRect) self.__data.layoutData.init(plot, rect) if ( not (options & self.IgnoreLegend) and plot.legend() and not plot.legend().isEmpty() ): self.__data.legendRect = self.layoutLegend(options, rect) region = QRegion(rect.toRect()) rect = QRectF( region.subtracted( QRegion(self.__data.legendRect.toRect()) ).boundingRect() ) if self.__data.legendPos == QwtPlot.LeftLegend: rect.setLeft(rect.left() + self.__data.spacing) elif self.__data.legendPos == QwtPlot.RightLegend: rect.setRight(rect.right() - self.__data.spacing) elif self.__data.legendPos == QwtPlot.TopLegend: rect.setTop(rect.top() + self.__data.spacing) elif self.__data.legendPos == QwtPlot.BottomLegend: rect.setBottom(rect.bottom() - self.__data.spacing) # +---+-----------+---+ # | Title | # +---+-----------+---+ # | | Axis | | # +---+-----------+---+ # | A | | A | # | x | Canvas | x | # | i | | i | # | s | | s | # +---+-----------+---+ # | | Axis | | # +---+-----------+---+ # | Footer | # +---+-----------+---+ # title, footer and axes include text labels. The height of each # label depends on its line breaks, that depend on the width # for the label. A line break in a horizontal text will reduce # the available width for vertical texts and vice versa. # expandLineBreaks finds the height/width for title, footer and axes # including all line breaks. dimTitle, dimFooter, dimAxes = self.expandLineBreaks(options, rect) if dimTitle > 0: self.__data.titleRect.setRect( rect.left(), rect.top(), rect.width(), dimTitle ) rect.setTop(self.__data.titleRect.bottom() + self.__data.spacing) if ( self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled ): self.__data.titleRect.setX(rect.left() + dimAxes[QwtPlot.yLeft]) self.__data.titleRect.setWidth( rect.width() - dimAxes[QwtPlot.yLeft] - dimAxes[QwtPlot.yRight] ) if dimFooter > 0: self.__data.footerRect.setRect( rect.left(), rect.bottom() - dimFooter, rect.width(), dimFooter ) rect.setBottom(self.__data.footerRect.top() - self.__data.spacing) if ( self.__data.layoutData.scale[QwtPlot.yLeft].isEnabled != self.__data.layoutData.scale[QwtPlot.yRight].isEnabled ): self.__data.footerRect.setX(rect.left() + dimAxes[QwtPlot.yLeft]) self.__data.footerRect.setWidth( rect.width() - dimAxes[QwtPlot.yLeft] - dimAxes[QwtPlot.yRight] ) self.__data.canvasRect.setRect( rect.x() + dimAxes[QwtPlot.yLeft], rect.y() + dimAxes[QwtPlot.xTop], rect.width() - dimAxes[QwtPlot.yRight] - dimAxes[QwtPlot.yLeft], rect.height() - dimAxes[QwtPlot.xBottom] - dimAxes[QwtPlot.xTop], ) for axis in QwtPlot.AXES: if dimAxes[axis]: dim = dimAxes[axis] scaleRect = self.__data.scaleRect[axis] scaleRect.setRect(*self.__data.canvasRect.getRect()) if axis == QwtPlot.yLeft: scaleRect.setX(self.__data.canvasRect.left() - dim) scaleRect.setWidth(dim) elif axis == QwtPlot.yRight: scaleRect.setX(self.__data.canvasRect.right()) scaleRect.setWidth(dim) elif axis == QwtPlot.xBottom: scaleRect.setY(self.__data.canvasRect.bottom()) scaleRect.setHeight(dim) elif axis == QwtPlot.xTop: scaleRect.setY(self.__data.canvasRect.top() - dim) scaleRect.setHeight(dim) scaleRect = scaleRect.normalized() # +---+-----------+---+ # | <- Axis -> | # +-^-+-----------+-^-+ # | | | | | | # | | | | # | A | | A | # | x | Canvas | x | # | i | | i | # | s | | s | # | | | | # | | | | | | # +-V-+-----------+-V-+ # | <- Axis -> | # +---+-----------+---+ # The ticks of the axes - not the labels above - should # be aligned to the canvas. So we try to use the empty # corners to extend the axes, so that the label texts # left/right of the min/max ticks are moved into them. self.alignScales(options, self.__data.canvasRect, self.__data.scaleRect) if not self.__data.legendRect.isEmpty(): self.__data.legendRect = self.alignLegend( self.__data.canvasRect, self.__data.legendRect )