class QwtText(object):
    """
    A class representing a text

    A `QwtText` is a text including a set of attributes how to render it.

      - Format:

      A text might include control sequences (f.e tags) describing
      how to render it. Each format (f.e MathML, TeX, Qt Rich Text)
      has its own set of control sequences, that can be handles by
      a special `QwtTextEngine` for this format.

      - Background:

      A text might have a background, defined by a `QPen` and `QBrush`
      to improve its visibility. The corners of the background might
      be rounded.

      - Font:

      A text might have an individual font.

      - Color

      A text might have an individual color.

      - Render Flags

      Flags from `Qt.AlignmentFlag` and `Qt.TextFlag` used like in
      `QPainter.drawText()`.

    ..seealso::

        :py:meth:`qwt.text.QwtTextEngine`,
        :py:meth:`qwt.text.QwtTextLabel`

    Text formats:

      * `QwtText.AutoText`:

        The text format is determined using `QwtTextEngine.mightRender()` for
        all available text engines in increasing order > PlainText.
        If none of the text engines can render the text is rendered
        like `QwtText.PlainText`.

      * `QwtText.PlainText`:

        Draw the text as it is, using a QwtPlainTextEngine.

      * `QwtText.RichText`:

        Use the Scribe framework (Qt Rich Text) to render the text.

      * `QwtText.MathMLText`:

        Use a MathML (http://en.wikipedia.org/wiki/MathML) render engine
        to display the text. The Qwt MathML extension offers such an engine
        based on the MathML renderer of the Qt solutions package.
        To enable MathML support the following code needs to be added to the
        application::

            QwtText.setTextEngine(QwtText.MathMLText, QwtMathMLTextEngine())

      * `QwtText.TeXText`:

        Use a TeX (http://en.wikipedia.org/wiki/TeX) render engine
        to display the text ( not implemented yet ).

      * `QwtText.OtherFormat`:

        The number of text formats can be extended using `setTextEngine`.
        Formats >= `QwtText.OtherFormat` are not used by Qwt.

    Paint attributes:

      * `QwtText.PaintUsingTextFont`: The text has an individual font.
      * `QwtText.PaintUsingTextColor`: The text has an individual color.
      * `QwtText.PaintBackground`: The text has an individual background.

    Layout attributes:

      * `QwtText.MinimumLayout`:

        Layout the text without its margins. This mode is useful if a
        text needs to be aligned accurately, like the tick labels of a scale.
        If `QwtTextEngine.textMargins` is not implemented for the format
        of the text, `MinimumLayout` has no effect.

    .. py:class:: QwtText([text=None], [textFormat=None], [other=None])

        :param str text: Text content
        :param int textFormat: Text format
        :param qwt.text.QwtText other: Object to copy (text and textFormat arguments are ignored)
    """

    # enum TextFormat
    AutoText, PlainText, RichText, MathMLText, TeXText = list(range(5))
    OtherFormat = 100

    # enum PaintAttribute
    PaintUsingTextFont = 0x01
    PaintUsingTextColor = 0x02
    PaintBackground = 0x04

    # enum LayoutAttribute
    MinimumLayout = 0x01

    # Optimization: a single text engine for all QwtText objects
    # (this is not how it's implemented in Qwt6 C++ library)
    __map = {PlainText: QwtPlainTextEngine(), RichText: QwtRichTextEngine()}

    def __init__(self, text=None, textFormat=None, other=None):
        self.__desktopwidget = None
        if text is None:
            text = ""
        if textFormat is None:
            textFormat = self.AutoText
        if other is not None:
            text = other
        if isinstance(text, QwtText):
            self.__data = text.__data
            self.__layoutCache = text.__layoutCache
        else:
            self.__data = QwtText_PrivateData()
            self.__data.text = text
            self.__data.textEngine = self.textEngine(text, textFormat)
            self.__layoutCache = QwtText_LayoutCache()

    @classmethod
    def make(
        cls,
        text=None,
        textformat=None,
        renderflags=None,
        font=None,
        family=None,
        pointsize=None,
        weight=None,
        color=None,
        borderradius=None,
        borderpen=None,
        brush=None,
    ):
        """
        Create and setup a new `QwtText` object (convenience function).

        :param str text: Text content
        :param int textformat: Text format
        :param int renderflags: Flags from `Qt.AlignmentFlag` and `Qt.TextFlag`
        :param font: Font
        :type font: QFont or None
        :param family: Font family (default: Helvetica)
        :type family: str or None
        :param pointsize: Font point size (default: 10)
        :type pointsize: int or None
        :param weight: Font weight (default: QFont.Normal)
        :type weight: int or None
        :param color: Pen color
        :type color: QColor or str or None
        :param borderradius: Radius for the corners of the border frame
        :type borderradius: float or None
        :param borderpen: Background pen
        :type borderpen: QPen or None
        :param brush: Background brush
        :type brush: QBrush or None

        .. seealso::

            :py:meth:`setText()`
        """
        item = cls(text=text, textFormat=textformat)
        if renderflags is not None:
            item.setRenderFlags(renderflags)
        if font is not None:
            item.setFont(font)
        elif family is not None or pointsize is not None or weight is not None:
            family = "Helvetica" if family is None else family
            pointsize = 10 if pointsize is None else pointsize
            weight = QFont.Normal if weight is None else weight
            item.setFont(QFont(family, pointsize, weight))
        if color is not None:
            item.setColor(qcolor_from_str(color, Qt.black))
        if borderradius is not None:
            item.setBorderRadius(borderradius)
        if borderpen is not None:
            item.setBorderPen(borderpen)
        if brush is not None:
            item.setBackgroundBrush(brush)
        return item

    @property
    def _desktopwidget(self):
        """
        Property used to store the Application Desktop Widget to avoid calling
        the `QApplication.desktop()" function more than necessary as its
        calling time is not negligible.
        """
        if self.__desktopwidget is None:
            self.__desktopwidget = QApplication.desktop()
        return self.__desktopwidget

    def __eq__(self, other):
        return (
            self.__data.renderFlags == other.__data.renderFlags
            and self.__data.text == other.__data.text
            and self.__data.font == other.__data.font
            and self.__data.color == other.__data.color
            and self.__data.borderRadius == other.__data.borderRadius
            and self.__data.borderPen == other.__data.borderPen
            and self.__data.backgroundBrush == other.__data.backgroundBrush
            and self.__data.paintAttributes == other.__data.paintAttributes
            and self.__data.textEngine == other.__data.textEngine
        )

    def __ne__(self, other):
        return not self.__eq__(other)

    def isEmpty(self):
        """
        :return: True if text is empty
        """
        return len(self.text()) == 0

    def setText(self, text, textFormat=None):
        """
        Assign a new text content

        :param str text: Text content
        :param int textFormat: Text format

        .. seealso::

            :py:meth:`text()`
        """
        if textFormat is None:
            textFormat = self.AutoText
        self.__data.text = text
        self.__data.textEngine = self.textEngine(text, textFormat)
        self.__layoutCache.invalidate()

    def text(self):
        """
        :return: Text content

        .. seealso::

            :py:meth:`setText()`
        """
        return self.__data.text

    def setRenderFlags(self, renderFlags):
        """
        Change the render flags

        The default setting is `Qt.AlignCenter`

        :param int renderFlags: Bitwise OR of the flags used like in `QPainter.drawText()`

        .. seealso::

            :py:meth:`renderFlags()`,
            :py:meth:`qwt.text.QwtTextEngine.draw()`
        """
        renderFlags = Qt.AlignmentFlag(renderFlags)
        if renderFlags != self.__data.renderFlags:
            self.__data.renderFlags = renderFlags
            self.__layoutCache.invalidate()

    def renderFlags(self):
        """
        :return: Render flags

        .. seealso::

            :py:meth:`setRenderFlags()`
        """
        return self.__data.renderFlags

    def setFont(self, font):
        """
        Set the font.

        :param QFont font: Font

        .. note::

            Setting the font might have no effect, when
            the text contains control sequences for setting fonts.

        .. seealso::

            :py:meth:`font()`, :py:meth:`usedFont()`
        """
        self.__data.font = font
        self.setPaintAttribute(self.PaintUsingTextFont)

    def font(self):
        """
        :return: Return the font

        .. seealso::

            :py:meth:`setFont()`, :py:meth:`usedFont()`
        """
        return self.__data.font

    def usedFont(self, defaultFont):
        """
        Return the font of the text, if it has one.
        Otherwise return defaultFont.

        :param QFont defaultFont: Default font
        :return: Font used for drawing the text

        .. seealso::

            :py:meth:`setFont()`, :py:meth:`font()`
        """
        if self.__data.paintAttributes & self.PaintUsingTextFont:
            return self.__data.font
        return defaultFont

    def setColor(self, color):
        """
        Set the pen color used for drawing the text.

        :param QColor color: Color

        .. note::

            Setting the color might have no effect, when
            the text contains control sequences for setting colors.

        .. seealso::

            :py:meth:`color()`, :py:meth:`usedColor()`
        """
        self.__data.color = QColor(color)
        self.setPaintAttribute(self.PaintUsingTextColor)

    def color(self):
        """
        :return: Return the pen color, used for painting the text

        .. seealso::

            :py:meth:`setColor()`, :py:meth:`usedColor()`
        """
        return self.__data.color

    def usedColor(self, defaultColor):
        """
        Return the color of the text, if it has one.
        Otherwise return defaultColor.

        :param QColor defaultColor: Default color
        :return: Color used for drawing the text

        .. seealso::

            :py:meth:`setColor()`, :py:meth:`color()`
        """
        if self.__data.paintAttributes & self.PaintUsingTextColor:
            return self.__data.color
        return defaultColor

    def setBorderRadius(self, radius):
        """
        Set the radius for the corners of the border frame

        :param float radius: Radius of a rounded corner

        .. seealso::

            :py:meth:`borderRadius()`, :py:meth:`setBorderPen()`,
            :py:meth:`setBackgroundBrush()`
        """
        self.__data.borderRadius = max([0.0, radius])

    def borderRadius(self):
        """
        :return: Radius for the corners of the border frame

        .. seealso::

            :py:meth:`setBorderRadius()`, :py:meth:`borderPen()`,
            :py:meth:`backgroundBrush()`
        """
        return self.__data.borderRadius

    def setBorderPen(self, pen):
        """
        Set the background pen

        :param QPen pen: Background pen

        .. seealso::

            :py:meth:`borderPen()`, :py:meth:`setBackgroundBrush()`
        """
        self.__data.borderPen = pen
        self.setPaintAttribute(self.PaintBackground)

    def borderPen(self):
        """
        :return: Background pen

        .. seealso::

            :py:meth:`setBorderPen()`, :py:meth:`backgroundBrush()`
        """
        return self.__data.borderPen

    def setBackgroundBrush(self, brush):
        """
        Set the background brush

        :param QBrush brush: Background brush

        .. seealso::

            :py:meth:`backgroundBrush()`, :py:meth:`setBorderPen()`
        """
        self.__data.backgroundBrush = brush
        self.setPaintAttribute(self.PaintBackground)

    def backgroundBrush(self):
        """
        :return: Background brush

        .. seealso::

            :py:meth:`setBackgroundBrush()`, :py:meth:`borderPen()`
        """
        return self.__data.backgroundBrush

    def setPaintAttribute(self, attribute, on=True):
        """
        Change a paint attribute

        :param int attribute: Paint attribute
        :param bool on: On/Off

        .. note::

            Used by `setFont()`, `setColor()`, `setBorderPen()`
            and `setBackgroundBrush()`

        .. seealso::

            :py:meth:`testPaintAttribute()`
        """
        if on:
            self.__data.paintAttributes |= attribute
        else:
            self.__data.paintAttributes &= ~attribute

    def testPaintAttribute(self, attribute):
        """
        Test a paint attribute

        :param int attribute: Paint attribute
        :return: True, if attribute is enabled

        .. seealso::

            :py:meth:`setPaintAttribute()`
        """
        return self.__data.paintAttributes & attribute

    def setLayoutAttribute(self, attribute, on=True):
        """
        Change a layout attribute

        :param int attribute: Layout attribute
        :param bool on: On/Off

        .. seealso::

            :py:meth:`testLayoutAttribute()`
        """
        if on:
            self.__data.layoutAttributes |= attribute
        else:
            self.__data.layoutAttributes &= ~attribute

    def testLayoutAttribute(self, attribute):
        """
        Test a layout attribute

        :param int attribute: Layout attribute
        :return: True, if attribute is enabled

        .. seealso::

            :py:meth:`setLayoutAttribute()`
        """
        return self.__data.layoutAttributes & attribute

    def heightForWidth(self, width, defaultFont=None):
        """
        Find the height for a given width

        :param float width: Width
        :param QFont defaultFont: Font, used for the calculation if the text has no font
        :return: Calculated height
        """
        if defaultFont is None:
            defaultFont = QFont()
        font = QFont(self.usedFont(defaultFont), self._desktopwidget)
        h = 0
        if self.__data.layoutAttributes & self.MinimumLayout:
            (left, right, top, bottom) = self.__data.textEngine.textMargins(font)
            h = self.__data.textEngine.heightForWidth(
                font, self.__data.renderFlags, self.__data.text, width + left + right
            )
            h -= top + bottom
        else:
            h = self.__data.textEngine.heightForWidth(
                font, self.__data.renderFlags, self.__data.text, width
            )
        return h

    def textSize(self, defaultFont):
        """
        Returns the size, that is needed to render text

        :param QFont defaultFont Font, used for the calculation if the text has no font
        :return: Caluclated size
        """
        font = QFont(self.usedFont(defaultFont), self._desktopwidget)
        if (
            not self.__layoutCache.textSize.isValid()
            or self.__layoutCache.font is not font
        ):
            self.__layoutCache.textSize = self.__data.textEngine.textSize(
                font, self.__data.renderFlags, self.__data.text
            )
            self.__layoutCache.font = font
        sz = self.__layoutCache.textSize
        if self.__data.layoutAttributes & self.MinimumLayout:
            (left, right, top, bottom) = self.__data.textEngine.textMargins(font)
            sz -= QSizeF(left + right, top + bottom)
        return sz

    def draw(self, painter, rect):
        """
        Draw a text into a rectangle

        :param QPainter painter: Painter
        :param QRectF rect: Rectangle
        """
        if self.__data.paintAttributes & self.PaintBackground:
            if (
                self.__data.borderPen != Qt.NoPen
                or self.__data.backgroundBrush != Qt.NoBrush
            ):
                painter.save()
                painter.setPen(self.__data.borderPen)
                painter.setBrush(self.__data.backgroundBrush)
                if self.__data.borderRadius == 0:
                    painter.drawRect(rect)
                else:
                    painter.setRenderHint(QPainter.Antialiasing, True)
                    painter.drawRoundedRect(
                        rect, self.__data.borderRadius, self.__data.borderRadius
                    )
                painter.restore()
        painter.save()
        if self.__data.paintAttributes & self.PaintUsingTextFont:
            painter.setFont(self.__data.font)
        if self.__data.paintAttributes & self.PaintUsingTextColor:
            if self.__data.color.isValid():
                painter.setPen(self.__data.color)
        expandedRect = rect
        if self.__data.layoutAttributes & self.MinimumLayout:
            font = QFont(painter.font(), self._desktopwidget)
            (left, right, top, bottom) = self.__data.textEngine.textMargins(font)
            expandedRect.setTop(rect.top() - top)
            expandedRect.setBottom(rect.bottom() + bottom)
            expandedRect.setLeft(rect.left() - left)
            expandedRect.setRight(rect.right() + right)
        self.__data.textEngine.draw(
            painter, expandedRect, self.__data.renderFlags, self.__data.text
        )
        painter.restore()

    def textEngine(self, text=None, format_=None):
        """
        Find the text engine for a text format

        In case of `QwtText.AutoText` the first text engine
        (beside `QwtPlainTextEngine`) is returned, where
        `QwtTextEngine.mightRender` returns true.
        If there is none `QwtPlainTextEngine` is returned.

        If no text engine is registered for the format `QwtPlainTextEngine`
        is returned.

        :param str text: Text, needed in case of AutoText
        :param int format: Text format
        :return: Corresponding text engine
        """
        if text is None:
            return self.__map.get(format_)
        elif format_ is not None:
            if format_ == QwtText.AutoText:
                for key, engine in list(self.__map.items()):
                    if key != QwtText.PlainText:
                        if engine and engine.mightRender(text):
                            return engine
            engine = self.__map.get(format_)
            if engine is not None:
                return engine
            return self.__map[QwtText.PlainText]
        else:
            raise TypeError(
                "%s().textEngine() takes 1 or 2 argument(s) (none"
                " given)" % self.__class__.__name__
            )

    def setTextEngine(self, format_, engine):
        """
        Assign/Replace a text engine for a text format

        With setTextEngine it is possible to extend `PythonQwt` with
        other types of text formats.

        For `QwtText.PlainText` it is not allowed to assign a engine to None.

        :param int format_: Text format
        :param qwt.text.QwtTextEngine engine: Text engine

        .. seealso::

            :py:meth:`setPaintAttribute()`

        .. warning::

            Using `QwtText.AutoText` does nothing.
        """
        if format_ == QwtText.AutoText:
            return
        if format_ == QwtText.PlainText and engine is None:
            return
        self.__map.setdefault(format_, engine)
 class QwtTextLabel(QFrame):
    """
    A Widget which displays a QwtText

    .. py:class:: QwtTextLabel(parent)

        :param QWidget parent: Parent widget

    .. py:class:: QwtTextLabel([text=None], [parent=None])
        :noindex:

        :param str text: Text
        :param QWidget parent: Parent widget
    """

    def __init__(self, *args):
        if len(args) == 0:
            text, parent = None, None
        elif len(args) == 1:
            if isinstance(args[0], QWidget):
                text = None
                (parent,) = args
            else:
                parent = None
                (text,) = args
        elif len(args) == 2:
            text, parent = args
        else:
            raise TypeError(
                "%s() takes 0, 1 or 2 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )
        super(QwtTextLabel, self).__init__(parent)
        self.init()
        if text is not None:
            self.__data.text = text

    def init(self):
        self.__data = QwtTextLabel_PrivateData()
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

    def setPlainText(self, text):
        """
        Interface for the designer plugin - does the same as setText()

        :param str text: Text

        .. seealso::

            :py:meth:`plainText()`
        """
        self.setText(QwtText(text))

    def plainText(self):
        """
        Interface for the designer plugin

        :return: Text as plain text

        .. seealso::

            :py:meth:`setPlainText()`
        """
        return self.__data.text.text()

    def setText(self, text, textFormat=QwtText.AutoText):
        """
        Change the label's text, keeping all other QwtText attributes

        :param text: New text
        :type text: qwt.text.QwtText or str
        :param int textFormat: Format of text

        .. seealso::

            :py:meth:`text()`
        """
        if isinstance(text, QwtText):
            self.__data.text = text
        else:
            self.__data.text.setText(text, textFormat)
        self.update()
        self.updateGeometry()

    def text(self):
        """
        :return: Return the text

        .. seealso::

            :py:meth:`setText()`
        """
        return self.__data.text

    def clear(self):
        """
        Clear the text and all `QwtText` attributes
        """
        self.__data.text = QwtText()
        self.update()
        self.updateGeometry()

    def indent(self):
        """
        :return: Label's text indent in pixels

        .. seealso::

            :py:meth:`setIndent()`
        """
        return self.__data.indent

    def setIndent(self, indent):
        """
        Set label's text indent in pixels

        :param int indent: Indentation in pixels

        .. seealso::

            :py:meth:`indent()`
        """
        if indent < 0:
            indent = 0
        self.__data.indent = indent
        self.update()
        self.updateGeometry()

    def margin(self):
        """
        :return: Label's text indent in pixels

        .. seealso::

            :py:meth:`setMargin()`
        """
        return self.__data.margin

    def setMargin(self, margin):
        """
        Set label's margin in pixels

        :param int margin: Margin in pixels

        .. seealso::

            :py:meth:`margin()`
        """
        self.__data.margin = margin
        self.update()
        self.updateGeometry()

    def sizeHint(self):
        """
        Return a size hint
        """
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        """
        Return a minimum size hint
        """
        sz = self.__data.text.textSize(self.font())
        mw = 2 * (self.frameWidth() + self.__data.margin)
        mh = mw
        indent = self.__data.indent
        if indent <= 0:
            indent = self.defaultIndent()
        if indent > 0:
            align = self.__data.text.renderFlags()
            if align & Qt.AlignLeft or align & Qt.AlignRight:
                mw += self.__data.indent
            elif align & Qt.AlignTop or align & Qt.AlignBottom:
                mh += self.__data.indent
        sz += QSizeF(mw, mh)
        return QSize(np.ceil(sz.width()), np.ceil(sz.height()))

    def heightForWidth(self, width):
        """
        :param int width: Width
        :return: Preferred height for this widget, given the width.
        """
        renderFlags = self.__data.text.renderFlags()
        indent = self.__data.indent
        if indent <= 0:
            indent = self.defaultIndent()
        width -= 2 * self.frameWidth()
        if renderFlags & Qt.AlignLeft or renderFlags & Qt.AlignRight:
            width -= indent
        height = np.ceil(self.__data.text.heightForWidth(width, self.font()))
        if renderFlags & Qt.AlignTop or renderFlags & Qt.AlignBottom:
            height += indent
        height += 2 * self.frameWidth()
        return height

    def paintEvent(self, event):
        painter = QPainter(self)
        if not self.contentsRect().contains(event.rect()):
            painter.save()
            painter.setClipRegion(event.region() & self.frameRect())
            self.drawFrame(painter)
            painter.restore()
        painter.setClipRegion(event.region() & self.contentsRect())
        self.drawContents(painter)

    def drawContents(self, painter):
        """
        Redraw the text and focus indicator

        :param QPainter painter: Painter
        """
        r = self.textRect()
        if r.isEmpty():
            return
        painter.setFont(self.font())
        painter.setPen(self.palette().color(QPalette.Active, QPalette.Text))
        self.drawText(painter, QRectF(r))
        if self.hasFocus():
            m = 2
            focusRect = self.contentsRect().adjusted(m, m, -m + 1, -m + 1)
            QwtPainter.drawFocusRect(painter, self, focusRect)

    def drawText(self, painter, textRect):
        """
        Redraw the text

        :param QPainter painter: Painter
        :param QRectF textRect: Text rectangle
        """
        self.__data.text.draw(painter, textRect)

    def textRect(self):
        """
        Calculate geometry for the text in widget coordinates

        :return: Geometry for the text
        """
        r = self.contentsRect()
        if not r.isEmpty() and self.__data.margin > 0:
            r.setRect(
                r.x() + self.__data.margin,
                r.y() + self.__data.margin,
                r.width() - 2 * self.__data.margin,
                r.height() - 2 * self.__data.margin,
            )
        if not r.isEmpty():
            indent = self.__data.indent
            if indent <= 0:
                indent = self.defaultIndent()
            if indent > 0:
                renderFlags = self.__data.text.renderFlags()
                if renderFlags & Qt.AlignLeft:
                    r.setX(r.x() + indent)
                elif renderFlags & Qt.AlignRight:
                    r.setWidth(r.width() - indent)
                elif renderFlags & Qt.AlignTop:
                    r.setY(r.y() + indent)
                elif renderFlags & Qt.AlignBottom:
                    r.setHeight(r.height() - indent)
        return r

    def defaultIndent(self):
        if self.frameWidth() <= 0:
            return 0
        if self.__data.text.testPaintAttribute(QwtText.PaintUsingTextFont):
            fnt = self.__data.text.font()
        else:
            fnt = self.font()
        return QFontMetrics(fnt).width("x") / 2
 class QwtScaleWidget(QWidget):
    """
    A Widget which contains a scale

    This Widget can be used to decorate composite widgets with
    a scale.

    Layout flags:

      * `QwtScaleWidget.TitleInverted`: The title of vertical scales is painted from top to bottom. Otherwise it is painted from bottom to top.

    .. py:class:: QwtScaleWidget([parent=None])

        Alignment default is `QwtScaleDraw.LeftScale`.

        :param parent: Parent widget
        :type parent: QWidget or None

    .. py:class:: QwtScaleWidget(align, parent)
        :noindex:

        :param int align: Alignment
        :param QWidget parent: Parent widget
    """

    scaleDivChanged = Signal()

    # enum LayoutFlag
    TitleInverted = 1

    def __init__(self, *args):
        self.__data = None
        align = QwtScaleDraw.LeftScale
        if len(args) == 0:
            parent = None
        elif len(args) == 1:
            (parent,) = args
        elif len(args) == 2:
            align, parent = args
        else:
            raise TypeError(
                "%s() takes 0, 1 or 2 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )
        super(QwtScaleWidget, self).__init__(parent)
        self.initScale(align)

    def initScale(self, align):
        """
        Initialize the scale

        :param int align: Alignment
        """
        self.__data = QwtScaleWidget_PrivateData()
        self.__data.layoutFlags = 0
        if align == QwtScaleDraw.RightScale:
            self.__data.layoutFlags |= self.TitleInverted

        self.__data.borderDist = [0, 0]
        self.__data.minBorderDist = [0, 0]
        self.__data.margin = 4
        self.__data.titleOffset = 0
        self.__data.spacing = 2

        self.__data.scaleDraw = QwtScaleDraw()
        self.__data.scaleDraw.setAlignment(align)
        self.__data.scaleDraw.setLength(10)

        self.__data.scaleDraw.setScaleDiv(
            QwtLinearScaleEngine().divideScale(0.0, 100.0, 10, 5)
        )

        self.__data.colorBar.colorMap = QwtLinearColorMap()
        self.__data.colorBar.isEnabled = False
        self.__data.colorBar.width = 10

        flags = Qt.AlignmentFlag(Qt.AlignHCenter | Qt.TextExpandTabs | Qt.TextWordWrap)
        self.__data.title.setRenderFlags(flags)
        self.__data.title.setFont(self.font())

        policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        if self.__data.scaleDraw.orientation() == Qt.Vertical:
            policy.transpose()

        self.setSizePolicy(policy)

        self.setAttribute(Qt.WA_WState_OwnSizePolicy, False)

    def setLayoutFlag(self, flag, on=True):
        """
        Toggle an layout flag

        :param int flag: Layout flag
        :param bool on: True/False

        .. seealso::

            :py:meth:`testLayoutFlag()`
        """
        if (self.__data.layoutFlags & flag != 0) != on:
            if on:
                self.__data.layoutFlags |= flag
            else:
                self.__data.layoutFlags &= ~flag
        self.update()

    def testLayoutFlag(self, flag):
        """
        Test a layout flag

        :param int flag: Layout flag
        :return: True/False

        .. seealso::

            :py:meth:`setLayoutFlag()`
        """
        return self.__data.layoutFlags & flag

    def setTitle(self, title):
        """
        Give title new text contents

        :param title: New title
        :type title: qwt.text.QwtText or str

        .. seealso::

            :py:meth:`title()`
        """
        if isinstance(title, QwtText):
            flags = title.renderFlags() & (~int(Qt.AlignTop | Qt.AlignBottom))
            title.setRenderFlags(flags)
            if title != self.__data.title:
                self.__data.title = title
                self.layoutScale()
        else:
            if self.__data.title.text() != title:
                self.__data.title.setText(title)
                self.layoutScale()

    def setAlignment(self, alignment):
        """
        Change the alignment

        :param int alignment: New alignment

        Valid alignment values: see :py:class:`qwt.scale_draw.QwtScaleDraw`

        .. seealso::

            :py:meth:`alignment()`
        """
        if self.__data.scaleDraw:
            self.__data.scaleDraw.setAlignment(alignment)
        if not self.testAttribute(Qt.WA_WState_OwnSizePolicy):
            policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
            if self.__data.scaleDraw.orientation() == Qt.Vertical:
                policy.transpose()
            self.setSizePolicy(policy)
            self.setAttribute(Qt.WA_WState_OwnSizePolicy, False)
        self.layoutScale()

    def alignment(self):
        """
        :return: position

        .. seealso::

            :py:meth:`setAlignment()`
        """
        if not self.scaleDraw():
            return QwtScaleDraw.LeftScale
        return self.scaleDraw().alignment()

    def setBorderDist(self, dist1, dist2):
        """
        Specify distances of the scale's endpoints from the
        widget's borders. The actual borders will never be less
        than minimum border distance.

        :param int dist1: Left or top Distance
        :param int dist2: Right or bottom distance

        .. seealso::

            :py:meth:`borderDist()`
        """
        if dist1 != self.__data.borderDist[0] or dist2 != self.__data.borderDist[1]:
            self.__data.borderDist = [dist1, dist2]
            self.layoutScale()

    def setMargin(self, margin):
        """
        Specify the margin to the colorBar/base line.

        :param int margin: Margin

        .. seealso::

            :py:meth:`margin()`
        """
        margin = max([0, margin])
        if margin != self.__data.margin:
            self.__data.margin = margin
            self.layoutScale()

    def setSpacing(self, spacing):
        """
        Specify the distance between color bar, scale and title

        :param int spacing: Spacing

        .. seealso::

            :py:meth:`spacing()`
        """
        spacing = max([0, spacing])
        if spacing != self.__data.spacing:
            self.__data.spacing = spacing
            self.layoutScale()

    def setLabelAlignment(self, alignment):
        """
        Change the alignment for the labels.

        :param int spacing: Spacing

        .. seealso::

            :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelAlignment()`,
            :py:meth:`setLabelRotation()`
        """
        self.__data.scaleDraw.setLabelAlignment(alignment)
        self.layoutScale()

    def setLabelRotation(self, rotation):
        """
        Change the rotation for the labels.

        :param float rotation: Rotation

        .. seealso::

            :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelRotation()`,
            :py:meth:`setLabelFlags()`
        """
        self.__data.scaleDraw.setLabelRotation(rotation)
        self.layoutScale()

    def setLabelAutoSize(self, state):
        """
        Set the automatic size option for labels (default: on).

        :param bool state: On/off

        .. seealso::

            :py:meth:`qwt.scale_draw.QwtScaleDraw.setLabelAutoSize()`
        """
        self.__data.scaleDraw.setLabelAutoSize(state)
        self.layoutScale()

    def setScaleDraw(self, scaleDraw):
        """
        Set a scale draw

        scaleDraw has to be created with new and will be deleted in
        class destructor or the next call of `setScaleDraw()`.
        scaleDraw will be initialized with the attributes of
        the previous scaleDraw object.

        :param qwt.scale_draw.QwtScaleDraw scaleDraw: ScaleDraw object

        .. seealso::

            :py:meth:`scaleDraw()`
        """
        if scaleDraw is None or scaleDraw == self.__data.scaleDraw:
            return
        sd = self.__data.scaleDraw
        if sd is not None:
            scaleDraw.setAlignment(sd.alignment())
            scaleDraw.setScaleDiv(sd.scaleDiv())
            transform = None
            if sd.scaleMap().transformation():
                transform = sd.scaleMap().transformation().copy()
            scaleDraw.setTransformation(transform)
        self.__data.scaleDraw = scaleDraw
        self.layoutScale()

    def scaleDraw(self):
        """
        :return: scaleDraw of this scale

        .. seealso::

            :py:meth:`qwt.scale_draw.QwtScaleDraw.setScaleDraw()`
        """
        return self.__data.scaleDraw

    def title(self):
        """
        :return: title

        .. seealso::

            :py:meth:`setTitle`
        """
        return self.__data.title

    def startBorderDist(self):
        """
        :return: start border distance

        .. seealso::

            :py:meth:`setBorderDist`
        """
        return self.__data.borderDist[0]

    def endBorderDist(self):
        """
        :return: end border distance

        .. seealso::

            :py:meth:`setBorderDist`
        """
        return self.__data.borderDist[1]

    def margin(self):
        """
        :return: margin

        .. seealso::

            :py:meth:`setMargin`
        """
        return self.__data.margin

    def spacing(self):
        """
        :return: distance between scale and title

        .. seealso::

            :py:meth:`setSpacing`
        """
        return self.__data.spacing

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setClipRegion(event.region())
        opt = QStyleOption()
        opt.initFrom(self)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
        self.draw(painter)

    def draw(self, painter):
        """
        Draw the scale

        :param QPainter painter: Painter
        """
        self.__data.scaleDraw.draw(painter, self.palette())
        if (
            self.__data.colorBar.isEnabled
            and self.__data.colorBar.width > 0
            and self.__data.colorBar.interval.isValid()
        ):
            self.drawColorBar(painter, self.colorBarRect(self.contentsRect()))

        r = self.contentsRect()
        if self.__data.scaleDraw.orientation() == Qt.Horizontal:
            r.setLeft(r.left() + self.__data.borderDist[0])
            r.setWidth(r.width() - self.__data.borderDist[1])
        else:
            r.setTop(r.top() + self.__data.borderDist[0])
            r.setHeight(r.height() - self.__data.borderDist[1])

        if not self.__data.title.isEmpty():
            self.drawTitle(painter, self.__data.scaleDraw.alignment(), r)

    def colorBarRect(self, rect):
        """
        Calculate the the rectangle for the color bar

        :param QRectF rect: Bounding rectangle for all components of the scale
        :return: Rectangle for the color bar
        """
        cr = QRectF(rect)
        if self.__data.scaleDraw.orientation() == Qt.Horizontal:
            cr.setLeft(cr.left() + self.__data.borderDist[0])
            cr.setWidth(cr.width() - self.__data.borderDist[1] + 1)
        else:
            cr.setTop(cr.top() + self.__data.borderDist[0])
            cr.setHeight(cr.height() - self.__data.borderDist[1] + 1)
        sda = self.__data.scaleDraw.alignment()
        if sda == QwtScaleDraw.LeftScale:
            cr.setLeft(cr.right() - self.__data.margin - self.__data.colorBar.width)
            cr.setWidth(self.__data.colorBar.width)
        elif sda == QwtScaleDraw.RightScale:
            cr.setLeft(cr.left() + self.__data.margin)
            cr.setWidth(self.__data.colorBar.width)
        elif sda == QwtScaleDraw.BottomScale:
            cr.setTop(cr.top() + self.__data.margin)
            cr.setHeight(self.__data.colorBar.width)
        elif sda == QwtScaleDraw.TopScale:
            cr.setTop(cr.bottom() - self.__data.margin - self.__data.colorBar.width)
            cr.setHeight(self.__data.colorBar.width)
        return cr

    def resizeEvent(self, event):
        self.layoutScale(False)

    def layoutScale(self, update_geometry=True):
        """
        Recalculate the scale's geometry and layout based on
        the current geometry and fonts.

        :param bool update_geometry: Notify the layout system and call update to redraw the scale
        """
        bd0, bd1 = self.getBorderDistHint()
        if self.__data.borderDist[0] > bd0:
            bd0 = self.__data.borderDist[0]
        if self.__data.borderDist[1] > bd1:
            bd1 = self.__data.borderDist[1]

        colorBarWidth = 0
        if self.__data.colorBar.isEnabled and self.__data.colorBar.interval.isValid():
            colorBarWidth = self.__data.colorBar.width + self.__data.spacing

        r = self.contentsRect()
        if self.__data.scaleDraw.orientation() == Qt.Vertical:
            y = r.top() + bd0
            length = r.height() - (bd0 + bd1)
            if self.__data.scaleDraw.alignment() == QwtScaleDraw.LeftScale:
                x = r.right() - 1.0 - self.__data.margin - colorBarWidth
            else:
                x = r.left() + self.__data.margin + colorBarWidth
        else:
            x = r.left() + bd0
            length = r.width() - (bd0 + bd1)
            if self.__data.scaleDraw.alignment() == QwtScaleDraw.BottomScale:
                y = r.top() + self.__data.margin + colorBarWidth
            else:
                y = r.bottom() - 1.0 - self.__data.margin - colorBarWidth

        self.__data.scaleDraw.move(x, y)
        self.__data.scaleDraw.setLength(length)

        extent = np.ceil(self.__data.scaleDraw.extent(self.font()))
        self.__data.titleOffset = (
            self.__data.margin + self.__data.spacing + colorBarWidth + extent
        )

        if update_geometry:
            self.updateGeometry()

            # The following was removed because it caused a high CPU usage 
            # in guiqwt.ImageWidget. The origin of these lines was an 
            # attempt to transpose PythonQwt from Qwt 6.1.2 to Qwt 6.1.5.

            #--> Begin of removed lines <--------------------------------------
            # #  for some reason updateGeometry does not send a LayoutRequest
            # #  event when the parent is not visible and has no layout
            # widget = self.parentWidget()
            # if widget and not widget.isVisible() and widget.layout() is None:
            #     if widget.testAttribute(Qt.WA_WState_Polished):
            #         QApplication.postEvent(
            #             self.parentWidget(), QEvent(QEvent.LayoutRequest)
            #         )
            #--> End of removed lines <----------------------------------------

            self.update()

    def drawColorBar(self, painter, rect):
        """
        Draw the color bar of the scale widget

        :param QPainter painter: Painter
        :param QRectF rect: Bounding rectangle for the color bar

        .. seealso::

            :py:meth:`setColorBarEnabled()`
        """
        if not self.__data.colorBar.interval.isValid():
            return
        sd = self.__data.scaleDraw
        QwtPainter.drawColorBar(
            painter,
            self.__data.colorBar.colorMap,
            self.__data.colorBar.interval.normalized(),
            sd.scaleMap(),
            sd.orientation(),
            rect,
        )

    def drawTitle(self, painter, align, rect):
        """
        Rotate and paint a title according to its position into a given rectangle.

        :param QPainter painter: Painter
        :param int align: Alignment
        :param QRectF rect: Bounding rectangle
        """
        r = rect
        flags = self.__data.title.renderFlags() & (
            ~int(Qt.AlignTop | Qt.AlignBottom | Qt.AlignVCenter)
        )
        if align == QwtScaleDraw.LeftScale:
            angle = -90.0
            flags |= Qt.AlignTop
            r.setRect(
                r.left(), r.bottom(), r.height(), r.width() - self.__data.titleOffset
            )
        elif align == QwtScaleDraw.RightScale:
            angle = -90.0
            flags |= Qt.AlignTop
            r.setRect(
                r.left() + self.__data.titleOffset,
                r.bottom(),
                r.height(),
                r.width() - self.__data.titleOffset,
            )
        elif align == QwtScaleDraw.BottomScale:
            angle = 0.0
            flags |= Qt.AlignBottom
            r.setTop(r.top() + self.__data.titleOffset)
        else:
            angle = 0.0
            flags |= Qt.AlignTop
            r.setBottom(r.bottom() - self.__data.titleOffset)

        if self.__data.layoutFlags & self.TitleInverted:
            if align in (QwtScaleDraw.LeftScale, QwtScaleDraw.RightScale):
                angle = -angle
                r.setRect(r.x() + r.height(), r.y() - r.width(), r.width(), r.height())

        painter.save()
        painter.setFont(self.font())
        painter.setPen(self.palette().color(QPalette.Text))

        painter.translate(r.x(), r.y())
        if angle != 0.0:
            painter.rotate(angle)

        title = self.__data.title
        title.setRenderFlags(flags)
        title.draw(painter, QRectF(0.0, 0.0, r.width(), r.height()))

        painter.restore()

    def scaleChange(self):
        """
        Notify a change of the scale

        This method can be overloaded by derived classes. The default
        implementation updates the geometry and repaints the widget.
        """
        self.layoutScale()

    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        o = self.__data.scaleDraw.orientation()
        length = 0
        mbd1, mbd2 = self.getBorderDistHint()
        length += max([0, self.__data.borderDist[0] - mbd1])
        length += max([0, self.__data.borderDist[1] - mbd2])
        length += self.__data.scaleDraw.minLength(self.font())

        dim = self.dimForLength(length, self.font())
        if length < dim:
            length = dim
            dim = self.dimForLength(length, self.font())

        size = QSize(length + 2, dim)
        if o == Qt.Vertical:
            size.transpose()

        left, right, top, bottom = self.getContentsMargins()
        return size + QSize(left + right, top + bottom)

    def titleHeightForWidth(self, width):
        """
        Find the height of the title for a given width.

        :param int width: Width
        :return: Height
        """
        return np.ceil(self.__data.title.heightForWidth(width, self.font()))

    def dimForLength(self, length, scaleFont):
        """
        Find the minimum dimension for a given length.
        dim is the height, length the width seen in direction of the title.

        :param int length: width for horizontal, height for vertical scales
        :param QFont scaleFont: Font of the scale
        :return: height for horizontal, width for vertical scales
        """
        extent = np.ceil(self.__data.scaleDraw.extent(scaleFont))
        dim = self.__data.margin + extent + 1
        if not self.__data.title.isEmpty():
            dim += self.titleHeightForWidth(length) + self.__data.spacing
        if self.__data.colorBar.isEnabled and self.__data.colorBar.interval.isValid():
            dim += self.__data.colorBar.width + self.__data.spacing
        return dim

    def getBorderDistHint(self):
        """
        Calculate a hint for the border distances.

        This member function calculates the distance
        of the scale's endpoints from the widget borders which
        is required for the mark labels to fit into the widget.
        The maximum of this distance an the minimum border distance
        is returned.

        :param int start: Return parameter for the border width at the beginning of the scale
        :param int end: Return parameter for the border width at the end of the scale

        .. warning::

            The minimum border distance depends on the font.

        .. seealso::

            :py:meth:`setMinBorderDist()`, :py:meth:`getMinBorderDist()`,
            :py:meth:`setBorderDist()`
        """
        start, end = self.__data.scaleDraw.getBorderDistHint(self.font())
        if start < self.__data.minBorderDist[0]:
            start = self.__data.minBorderDist[0]
        if end < self.__data.minBorderDist[1]:
            end = self.__data.minBorderDist[1]
        return start, end

    def setMinBorderDist(self, start, end):
        """
        Set a minimum value for the distances of the scale's endpoints from
        the widget borders. This is useful to avoid that the scales
        are "jumping", when the tick labels or their positions change
        often.

        :param int start: Minimum for the start border
        :param int end: Minimum for the end border

        .. seealso::

            :py:meth:`getMinBorderDist()`, :py:meth:`getBorderDistHint()`
        """
        self.__data.minBorderDist = [start, end]

    def getMinBorderDist(self):
        """
        Get the minimum value for the distances of the scale's endpoints from
        the widget borders.

        :param int start: Return parameter for the border width at the beginning of the scale
        :param int end: Return parameter for the border width at the end of the scale

        .. seealso::

            :py:meth:`setMinBorderDist()`, :py:meth:`getBorderDistHint()`
        """
        return self.__data.minBorderDist

    def setScaleDiv(self, scaleDiv):
        """
        Assign a scale division

        The scale division determines where to set the tick marks.

        :param qwt.scale_div.QwtScaleDiv scaleDiv: Scale Division

        .. seealso::

            For more information about scale divisions,
            see :py:class:`qwt.scale_div.QwtScaleDiv`.
        """
        sd = self.__data.scaleDraw
        if sd.scaleDiv() != scaleDiv:
            sd.setScaleDiv(scaleDiv)
            self.layoutScale()
            self.scaleDivChanged.emit()

    def setTransformation(self, transformation):
        """
        Set the transformation

        :param qwt.transform.Transform transformation: Transformation

        .. seealso::

            :py:meth:`qwt.scale_draw.QwtAbstractScaleDraw.scaleDraw()`,
            :py:class:`qwt.scale_map.QwtScaleMap`
        """
        self.__data.scaleDraw.setTransformation(transformation)
        self.layoutScale()

    def setColorBarEnabled(self, on):
        """
        En/disable a color bar associated to the scale

        :param bool on: On/Off

        .. seealso::

            :py:meth:`isColorBarEnabled()`, :py:meth:`setColorBarWidth()`
        """
        if on != self.__data.colorBar.isEnabled:
            self.__data.colorBar.isEnabled = on
            self.layoutScale()

    def isColorBarEnabled(self):
        """
        :return: True, when the color bar is enabled

        .. seealso::

            :py:meth:`setColorBarEnabled()`, :py:meth:`setColorBarWidth()`
        """
        return self.__data.colorBar.isEnabled

    def setColorBarWidth(self, width):
        """
        Set the width of the color bar

        :param int width: Width

        .. seealso::

            :py:meth:`colorBarWidth()`, :py:meth:`setColorBarEnabled()`
        """
        if width != self.__data.colorBar.width:
            self.__data.colorBar.width = width
            if self.isColorBarEnabled():
                self.layoutScale()

    def colorBarWidth(self):
        """
        :return: Width of the color bar

        .. seealso::

            :py:meth:`setColorBarWidth()`, :py:meth:`setColorBarEnabled()`
        """
        return self.__data.colorBar.width

    def colorBarInterval(self):
        """
        :return: Value interval for the color bar

        .. seealso::

            :py:meth:`setColorMap()`, :py:meth:`colorMap()`
        """
        return self.__data.colorBar.interval

    def setColorMap(self, interval, colorMap):
        """
        Set the color map and value interval, that are used for displaying
        the color bar.

        :param qwt.interval.QwtInterval interval: Value interval
        :param qwt.color_map.QwtColorMap colorMap: Color map

        .. seealso::

            :py:meth:`colorMap()`, :py:meth:`colorBarInterval()`
        """
        self.__data.colorBar.interval = interval
        if colorMap != self.__data.colorBar.colorMap:
            self.__data.colorBar.colorMap = colorMap
        if self.isColorBarEnabled():
            self.layoutScale()

    def colorMap(self):
        """
        :return: Color map

        .. seealso::

            :py:meth:`setColorMap()`, :py:meth:`colorBarInterval()`
        """
        return self.__data.colorBar.colorMap
 class QwtScaleDraw(QwtAbstractScaleDraw):
    """
    A class for drawing scales

    QwtScaleDraw can be used to draw linear or logarithmic scales.
    A scale has a position, an alignment and a length, which can be specified .
    The labels can be rotated and aligned
    to the ticks using `setLabelRotation()` and `setLabelAlignment()`.

    After a scale division has been specified as a QwtScaleDiv object
    using `QwtAbstractScaleDraw.setScaleDiv(scaleDiv)`,
    the scale can be drawn with the `QwtAbstractScaleDraw.draw()` member.

    Alignment of the scale draw:

      * `QwtScaleDraw.BottomScale`: The scale is below
      * `QwtScaleDraw.TopScale`: The scale is above
      * `QwtScaleDraw.LeftScale`: The scale is left
      * `QwtScaleDraw.RightScale`: The scale is right

    .. py:class:: QwtScaleDraw()

        The range of the scale is initialized to [0, 100],
        The position is at (0, 0) with a length of 100.
        The orientation is `QwtAbstractScaleDraw.Bottom`.
    """

    # enum Alignment
    BottomScale, TopScale, LeftScale, RightScale = list(range(4))
    Flags = (
        Qt.AlignHCenter | Qt.AlignBottom,  # BottomScale
        Qt.AlignHCenter | Qt.AlignTop,  # TopScale
        Qt.AlignLeft | Qt.AlignVCenter,  # LeftScale
        Qt.AlignRight | Qt.AlignVCenter,  # RightScale
    )

    def __init__(self):
        QwtAbstractScaleDraw.__init__(self)
        self.__data = QwtScaleDraw_PrivateData()
        self.setLength(100)
        self._max_label_sizes = {}

    def alignment(self):
        """
        :return: Alignment of the scale

        .. seealso::

            :py:meth:`setAlignment()`
        """
        return self.__data.alignment

    def setAlignment(self, align):
        """
        Set the alignment of the scale

        :param int align: Alignment of the scale

        Alignment of the scale draw:

          * `QwtScaleDraw.BottomScale`: The scale is below
          * `QwtScaleDraw.TopScale`: The scale is above
          * `QwtScaleDraw.LeftScale`: The scale is left
          * `QwtScaleDraw.RightScale`: The scale is right

         The default alignment is `QwtScaleDraw.BottomScale`

        .. seealso::

            :py:meth:`alignment()`
        """
        self.__data.alignment = align

    def orientation(self):
        """
        Return the orientation

        TopScale, BottomScale are horizontal (`Qt.Horizontal`) scales,
        LeftScale, RightScale are vertical (`Qt.Vertical`) scales.

        :return: Orientation of the scale

        .. seealso::

            :py:meth:`alignment()`
        """
        if self.__data.alignment in (self.TopScale, self.BottomScale):
            return Qt.Horizontal
        elif self.__data.alignment in (self.LeftScale, self.RightScale):
            return Qt.Vertical

    def getBorderDistHint(self, font):
        """
        Determine the minimum border distance

        This member function returns the minimum space
        needed to draw the mark labels at the scale's endpoints.

        :param QFont font: Font
        :return: tuple `(start, end)`

        Returned tuple:

            * start: Start border distance
            * end: End border distance
        """
        start, end = 0, 1.0

        if not self.hasComponent(QwtAbstractScaleDraw.Labels):
            return start, end

        ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
        if len(ticks) == 0:
            return start, end

        minTick = ticks[0]
        minPos = self.scaleMap().transform(minTick)
        maxTick = minTick
        maxPos = minPos

        for tick in ticks:
            tickPos = self.scaleMap().transform(tick)
            if tickPos < minPos:
                minTick = tick
                minPos = tickPos
            if tickPos > self.scaleMap().transform(maxTick):
                maxTick = tick
                maxPos = tickPos

        s = 0.0
        e = 0.0
        if self.orientation() == Qt.Vertical:
            s = -self.labelRect(font, minTick).top()
            s -= abs(minPos - round(self.scaleMap().p2()))

            e = self.labelRect(font, maxTick).bottom()
            e -= abs(maxPos - self.scaleMap().p1())
        else:
            s = -self.labelRect(font, minTick).left()
            s -= abs(minPos - self.scaleMap().p1())

            e = self.labelRect(font, maxTick).right()
            e -= abs(maxPos - self.scaleMap().p2())

        return max(ceil(s), 0), max(ceil(e), 0)

    def minLabelDist(self, font):
        """
        Determine the minimum distance between two labels, that is necessary
        that the texts don't overlap.

        :param QFont font: Font
        :return: The maximum width of a label

        .. seealso::

            :py:meth:`getBorderDistHint()`
        """
        if not self.hasComponent(QwtAbstractScaleDraw.Labels):
            return 0

        ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
        if not ticks:
            return 0

        fm = QFontMetrics(font)
        vertical = self.orientation() == Qt.Vertical

        bRect1 = QRectF()
        bRect2 = self.labelRect(font, ticks[0])
        if vertical:
            bRect2.setRect(-bRect2.bottom(), 0.0, bRect2.height(), bRect2.width())

        maxDist = 0.0

        for tick in ticks:
            bRect1 = bRect2
            bRect2 = self.labelRect(font, tick)
            if vertical:
                bRect2.setRect(-bRect2.bottom(), 0.0, bRect2.height(), bRect2.width())

            dist = fm.leading()
            if bRect1.right() > 0:
                dist += bRect1.right()
            if bRect2.left() < 0:
                dist += -bRect2.left()

            if dist > maxDist:
                maxDist = dist

        angle = qwtRadians(self.labelRotation())
        if vertical:
            angle += np.pi / 2

        sinA = np.sin(angle)
        if qFuzzyCompare(sinA + 1.0, 1.0):
            return np.ceil(maxDist)

        fmHeight = fm.ascent() - 2

        labelDist = fmHeight / np.sin(angle) * np.cos(angle)
        if labelDist < 0:
            labelDist = -labelDist

        if labelDist > maxDist:
            labelDist = maxDist

        if labelDist < fmHeight:
            labelDist = fmHeight

        return np.ceil(labelDist)

    def extent(self, font):
        """
        Calculate the width/height that is needed for a
        vertical/horizontal scale.

        The extent is calculated from the pen width of the backbone,
        the major tick length, the spacing and the maximum width/height
        of the labels.

        :param QFont font: Font used for painting the labels
        :return: Extent

        .. seealso::

            :py:meth:`minLength()`
        """
        d = 0.0
        if self.hasComponent(QwtAbstractScaleDraw.Labels):
            if self.orientation() == Qt.Vertical:
                d = self.maxLabelWidth(font)
            else:
                d = self.maxLabelHeight(font)
            if d > 0:
                d += self.spacing()
        if self.hasComponent(QwtAbstractScaleDraw.Ticks):
            d += self.maxTickLength()
        if self.hasComponent(QwtAbstractScaleDraw.Backbone):
            pw = max([1, self.penWidth()])
            d += pw
        return max([d, self.minimumExtent()])

    def minLength(self, font):
        """
        Calculate the minimum length that is needed to draw the scale

        :param QFont font: Font used for painting the labels
        :return: Minimum length that is needed to draw the scale

        .. seealso::

            :py:meth:`extent()`
        """
        startDist, endDist = self.getBorderDistHint(font)
        sd = self.scaleDiv()
        minorCount = len(sd.ticks(QwtScaleDiv.MinorTick)) + len(
            sd.ticks(QwtScaleDiv.MediumTick)
        )
        majorCount = len(sd.ticks(QwtScaleDiv.MajorTick))
        lengthForLabels = 0
        if self.hasComponent(QwtAbstractScaleDraw.Labels):
            lengthForLabels = self.minLabelDist(font) * majorCount
        lengthForTicks = 0
        if self.hasComponent(QwtAbstractScaleDraw.Ticks):
            pw = max([1, self.penWidth()])
            lengthForTicks = np.ceil((majorCount + minorCount) * (pw + 1.0))
        return startDist + endDist + max([lengthForLabels, lengthForTicks])

    def labelPosition(self, value):
        """
        Find the position, where to paint a label

        The position has a distance that depends on the length of the ticks
        in direction of the `alignment()`.

        :param float value: Value
        :return: Position, where to paint a label
        """
        tval = self.scaleMap().transform(value)
        dist = self.spacing()
        if self.hasComponent(QwtAbstractScaleDraw.Backbone):
            dist += max([1, self.penWidth()])
        if self.hasComponent(QwtAbstractScaleDraw.Ticks):
            dist += self.tickLength(QwtScaleDiv.MajorTick)

        px = 0
        py = 0
        if self.alignment() == self.RightScale:
            px = self.__data.pos.x() + dist
            py = tval
        elif self.alignment() == self.LeftScale:
            px = self.__data.pos.x() - dist
            py = tval
        elif self.alignment() == self.BottomScale:
            px = tval
            py = self.__data.pos.y() + dist
        elif self.alignment() == self.TopScale:
            px = tval
            py = self.__data.pos.y() - dist

        return QPointF(px, py)

    def drawTick(self, painter, value, len_):
        """
        Draw a tick

        :param QPainter painter: Painter
        :param float value: Value of the tick
        :param float len: Length of the tick

        .. seealso::

            :py:meth:`drawBackbone()`, :py:meth:`drawLabel()`
        """
        if len_ <= 0:
            return
        pos = self.__data.pos
        tval = self.scaleMap().transform(value)
        pw = self.penWidth()
        a = 0
        if self.alignment() == self.LeftScale:
            x1 = pos.x() + a
            x2 = pos.x() + a - pw - len_
            painter.drawLine(x1, tval, x2, tval)
        elif self.alignment() == self.RightScale:
            x1 = pos.x()
            x2 = pos.x() + pw + len_
            painter.drawLine(x1, tval, x2, tval)
        elif self.alignment() == self.BottomScale:
            y1 = pos.y()
            y2 = pos.y() + pw + len_
            painter.drawLine(tval, y1, tval, y2)
        elif self.alignment() == self.TopScale:
            y1 = pos.y() + a
            y2 = pos.y() - pw - len_ + a
            painter.drawLine(tval, y1, tval, y2)

    def drawBackbone(self, painter):
        """
        Draws the baseline of the scale

        :param QPainter painter: Painter

        .. seealso::

            :py:meth:`drawTick()`, :py:meth:`drawLabel()`
        """
        pos = self.__data.pos
        len_ = self.__data.len
        off = 0.5 * self.penWidth()
        if self.alignment() == self.LeftScale:
            x = pos.x() - off
            painter.drawLine(x, pos.y(), x, pos.y() + len_)
        elif self.alignment() == self.RightScale:
            x = pos.x() + off
            painter.drawLine(x, pos.y(), x, pos.y() + len_)
        elif self.alignment() == self.TopScale:
            y = pos.y() - off
            painter.drawLine(pos.x(), y, pos.x() + len_, y)
        elif self.alignment() == self.BottomScale:
            y = pos.y() + off
            painter.drawLine(pos.x(), y, pos.x() + len_, y)

    def move(self, *args):
        """
        Move the position of the scale

        The meaning of the parameter pos depends on the alignment:

          * `QwtScaleDraw.LeftScale`:

            The origin is the topmost point of the backbone. The backbone is a
            vertical line. Scale marks and labels are drawn at the left of the
            backbone.

          * `QwtScaleDraw.RightScale`:

            The origin is the topmost point of the backbone. The backbone is a
            vertical line. Scale marks and labels are drawn at the right of
            the backbone.

          * `QwtScaleDraw.TopScale`:

            The origin is the leftmost point of the backbone. The backbone is
            a horizontal line. Scale marks and labels are drawn above the
            backbone.

          * `QwtScaleDraw.BottomScale`:

            The origin is the leftmost point of the backbone. The backbone is
            a horizontal line Scale marks and labels are drawn below the
            backbone.

        .. py:method:: move(x, y)
            :noindex:

            :param float x: X coordinate
            :param float y: Y coordinate

        .. py:method:: move(pos)
            :noindex:

            :param QPointF pos: position

        .. seealso::

            :py:meth:`pos()`, :py:meth:`setLength()`
        """
        if len(args) == 2:
            x, y = args
            self.move(QPointF(x, y))
        elif len(args) == 1:
            (pos,) = args
            self.__data.pos = pos
            self.updateMap()
        else:
            raise TypeError(
                "%s().move() takes 1 or 2 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )

    def pos(self):
        """
        :return: Origin of the scale

        .. seealso::

            :py:meth:`pos()`, :py:meth:`setLength()`
        """
        return self.__data.pos

    def setLength(self, length):
        """
        Set the length of the backbone.

        The length doesn't include the space needed for overlapping labels.

        :param float length: Length of the backbone

        .. seealso::

            :py:meth:`move()`, :py:meth:`minLabelDist()`
        """
        if length >= 0 and length < 10:
            length = 10
        if length < 0 and length > -10:
            length = -10
        self.__data.len = length
        self.updateMap()

    def length(self):
        """
        :return: the length of the backbone

        .. seealso::

            :py:meth:`setLength()`, :py:meth:`pos()`
        """
        return self.__data.len

    def drawLabel(self, painter, value):
        """
        Draws the label for a major scale tick

        :param QPainter painter: Painter
        :param float value: Value

        .. seealso::

            :py:meth:`drawTick()`, :py:meth:`drawBackbone()`,
            :py:meth:`boundingLabelRect()`
        """
        lbl = self.tickLabel(painter.font(), value)
        if lbl is None or lbl.isEmpty():
            return
        pos = self.labelPosition(value)
        labelSize = lbl.textSize(painter.font())
        transform = self.labelTransformation(pos, labelSize)
        painter.save()
        painter.setWorldTransform(transform, True)
        lbl.draw(painter, QRect(QPoint(0, 0), labelSize.toSize()))
        painter.restore()

    def boundingLabelRect(self, font, value):
        """
        Find the bounding rectangle for the label.

        The coordinates of the rectangle are absolute (calculated from
        `pos()`) in direction of the tick.

        :param QFont font: Font used for painting
        :param float value: Value
        :return: Bounding rectangle

        .. seealso::

            :py:meth:`labelRect()`
        """
        lbl = self.tickLabel(font, value)
        if lbl.isEmpty():
            return QRect()
        pos = self.labelPosition(value)
        labelSize = lbl.textSize(font)
        transform = self.labelTransformation(pos, labelSize)
        return transform.mapRect(QRect(QPoint(0, 0), labelSize.toSize()))

    def labelTransformation(self, pos, size):
        """
        Calculate the transformation that is needed to paint a label
        depending on its alignment and rotation.

        :param QPointF pos: Position where to paint the label
        :param QSizeF size: Size of the label
        :return: Transformation matrix

        .. seealso::

            :py:meth:`setLabelAlignment()`, :py:meth:`setLabelRotation()`
        """
        transform = QTransform()
        transform.translate(pos.x(), pos.y())
        transform.rotate(self.labelRotation())

        flags = self.labelAlignment()
        if flags == 0:
            flags = self.Flags[self.alignment()]

        if flags & Qt.AlignLeft:
            x = -size.width()
        elif flags & Qt.AlignRight:
            x = 0.0
        else:
            x = -(0.5 * size.width())

        if flags & Qt.AlignTop:
            y = -size.height()
        elif flags & Qt.AlignBottom:
            y = 0
        else:
            y = -(0.5 * size.height())

        transform.translate(x, y)

        return transform

    def labelRect(self, font, value):
        """
        Find the bounding rectangle for the label. The coordinates of
        the rectangle are relative to spacing + tick length from the backbone
        in direction of the tick.

        :param QFont font: Font used for painting
        :param float value: Value
        :return: Bounding rectangle that is needed to draw a label
        """
        lbl = self.tickLabel(font, value)
        if not lbl or lbl.isEmpty():
            return QRectF(0.0, 0.0, 0.0, 0.0)
        pos = self.labelPosition(value)
        labelSize = lbl.textSize(font)
        transform = self.labelTransformation(pos, labelSize)
        br = transform.mapRect(QRectF(QPointF(0, 0), labelSize))
        br.translate(-pos.x(), -pos.y())
        return br

    def labelSize(self, font, value):
        """
        Calculate the size that is needed to draw a label

        :param QFont font: Label font
        :param float value: Value
        :return: Size that is needed to draw a label
        """
        return self.labelRect(font, value).size()

    def setLabelRotation(self, rotation):
        """
        Rotate all labels.

        When changing the rotation, it might be necessary to
        adjust the label flags too. Finding a useful combination is
        often the result of try and error.

        :param float rotation: Angle in degrees. When changing the label rotation, the label flags often needs to be adjusted too.

        .. seealso::

            :py:meth:`setLabelAlignment()`, :py:meth:`labelRotation()`,
            :py:meth:`labelAlignment()`
        """
        self.__data.labelRotation = rotation

    def labelRotation(self):
        """
        :return: the label rotation

        .. seealso::

            :py:meth:`setLabelRotation()`, :py:meth:`labelAlignment()`
        """
        return self.__data.labelRotation

    def setLabelAlignment(self, alignment):
        """
        Change the label flags

        Labels are aligned to the point tick length + spacing away from the
        backbone.

        The alignment is relative to the orientation of the label text.
        In case of an flags of 0 the label will be aligned
        depending on the orientation of the scale:

            * `QwtScaleDraw.TopScale`: `Qt.AlignHCenter | Qt.AlignTop`
            * `QwtScaleDraw.BottomScale`: `Qt.AlignHCenter | Qt.AlignBottom`
            * `QwtScaleDraw.LeftScale`: `Qt.AlignLeft | Qt.AlignVCenter`
            * `QwtScaleDraw.RightScale`: `Qt.AlignRight | Qt.AlignVCenter`

        Changing the alignment is often necessary for rotated labels.

        :param Qt.Alignment alignment Or'd `Qt.AlignmentFlags`

        .. seealso::

            :py:meth:`setLabelRotation()`, :py:meth:`labelRotation()`,
            :py:meth:`labelAlignment()`

        .. warning::

            The various alignments might be confusing. The alignment of the
            label is not the alignment of the scale and is not the alignment
            of the flags (`QwtText.flags()`) returned from
            `QwtAbstractScaleDraw.label()`.
        """
        self.__data.labelAlignment = alignment

    def labelAlignment(self):
        """
        :return: the label flags

        .. seealso::

            :py:meth:`setLabelAlignment()`, :py:meth:`labelRotation()`
        """
        return self.__data.labelAlignment

    def setLabelAutoSize(self, state):
        """
        Set label automatic size option state

        When drawing text labels, if automatic size mode is enabled (default
        behavior), the axes are drawn in order to optimize layout space and
        depends on text label individual sizes. Otherwise, width and height
        won't change when axis range is changing.

        This option is not implemented in Qwt C++ library: this may be used
        either as an optimization (updating plot layout is faster when this
        option is enabled) or as an appearance preference (with Qwt default
        behavior, the size of axes may change when zooming and/or panning
        plot canvas which in some cases may not be desired).

        :param bool state: On/off

        .. seealso::

            :py:meth:`labelAutoSize()`
        """
        self.__data.labelAutoSize = state

    def labelAutoSize(self):
        """
        :return: True if automatic size option is enabled for labels

        .. seealso::

            :py:meth:`setLabelAutoSize()`
        """
        return self.__data.labelAutoSize

    def _get_max_label_size(self, font):
        key = (font.toString(), self.labelRotation())
        size = self._max_label_sizes.get(key)
        if size is None:
            size = self.labelSize(font, -999999)  # -999999 is the biggest label
            size.setWidth(np.ceil(size.width()))
            size.setHeight(np.ceil(size.height()))
            return self._max_label_sizes.setdefault(key, size)
        else:
            return size

    def maxLabelWidth(self, font):
        """
        :param QFont font: Font
        :return: the maximum width of a label
        """
        ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
        if not ticks:
            return 0
        if self.labelAutoSize():
            vmax = sorted(
                [v for v in ticks if self.scaleDiv().contains(v)],
                key=lambda obj: len(QLocale().toString(obj)),
            )[-1]
            return np.ceil(self.labelSize(font, vmax).width())
            ## Original implementation (closer to Qwt's C++ code, but slower):
            # return np.ceil(max([self.labelSize(font, v).width()
            #                for v in ticks if self.scaleDiv().contains(v)]))
        else:
            return self._get_max_label_size(font).width()

    def maxLabelHeight(self, font):
        """
        :param QFont font: Font
        :return: the maximum height of a label
        """
        ticks = self.scaleDiv().ticks(QwtScaleDiv.MajorTick)
        if not ticks:
            return 0
        if self.labelAutoSize():
            vmax = sorted(
                [v for v in ticks if self.scaleDiv().contains(v)],
                key=lambda obj: len(QLocale().toString(obj)),
            )[-1]
            return np.ceil(self.labelSize(font, vmax).height())
            ## Original implementation (closer to Qwt's C++ code, but slower):
            # return np.ceil(max([self.labelSize(font, v).height()
            #                for v in ticks if self.scaleDiv().contains(v)]))
        else:
            return self._get_max_label_size(font).height()

    def updateMap(self):
        pos = self.__data.pos
        len_ = self.__data.len
        sm = self.scaleMap()
        if self.orientation() == Qt.Vertical:
            sm.setPaintInterval(pos.y() + len_, pos.y())
        else:
            sm.setPaintInterval(pos.x(), pos.x() + len_)
 class QwtLinearScaleEngine(QwtScaleEngine):
    """
    A scale engine for linear scales

    The step size will fit into the pattern
    \f$\left\{ 1,2,5\right\} \cdot 10^{n}\f$, where n is an integer.
    """

    def __init__(self, base=10):
        super(QwtLinearScaleEngine, self).__init__(base)

    def autoScale(self, maxNumSteps, x1, x2, stepSize):
        """
        Align and divide an interval

        :param int maxNumSteps: Max. number of steps
        :param float x1: First limit of the interval (In/Out)
        :param float x2: Second limit of the interval (In/Out)
        :param float stepSize: Step size
        :return: tuple (x1, x2, stepSize)

        .. seealso::

            :py:meth:`setAttribute()`
        """
        interval = QwtInterval(x1, x2)
        interval = interval.normalized()
        interval.setMinValue(interval.minValue() - self.lowerMargin())
        interval.setMaxValue(interval.maxValue() + self.upperMargin())
        if self.testAttribute(QwtScaleEngine.Symmetric):
            interval = interval.symmetrize(self.reference())
        if self.testAttribute(QwtScaleEngine.IncludeReference):
            interval = interval.extend(self.reference())
        if interval.width() == 0.0:
            interval = self.buildInterval(interval.minValue())
        stepSize = divideInterval(interval.width(), max([maxNumSteps, 1]), self.base())
        if not self.testAttribute(QwtScaleEngine.Floating):
            interval = self.align(interval, stepSize)
        x1 = interval.minValue()
        x2 = interval.maxValue()
        if self.testAttribute(QwtScaleEngine.Inverted):
            x1, x2 = x2, x1
            stepSize = -stepSize
        return x1, x2, stepSize

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

        :param float x1: First interval limit
        :param float x2: Second interval limit
        :param int maxMajorSteps: Maximum for the number of major steps
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size. If stepSize == 0.0, the scaleEngine calculates one
        :return: Calculated scale division
        """
        interval = QwtInterval(x1, x2).normalized()
        if interval.width() <= 0:
            return QwtScaleDiv()
        stepSize = abs(stepSize)
        if stepSize == 0.0:
            if maxMajorSteps < 1:
                maxMajorSteps = 1
            stepSize = divideInterval(interval.width(), maxMajorSteps, self.base())
        scaleDiv = QwtScaleDiv()
        if stepSize != 0.0:
            ticks = self.buildTicks(interval, stepSize, maxMinorSteps)
            scaleDiv = QwtScaleDiv(interval, ticks)
        if x1 > x2:
            scaleDiv.invert()
        return scaleDiv

    def buildTicks(self, interval, stepSize, maxMinorSteps):
        """
        Calculate ticks for an interval

        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :param int maxMinorSteps: Maximum number of minor steps
        :return: Calculated ticks
        """
        ticks = [[] for _i in range(QwtScaleDiv.NTickTypes)]
        boundingInterval = self.align(interval, stepSize)
        ticks[QwtScaleDiv.MajorTick] = self.buildMajorTicks(boundingInterval, stepSize)
        if maxMinorSteps > 0:
            self.buildMinorTicks(ticks, maxMinorSteps, stepSize)
        for i in range(QwtScaleDiv.NTickTypes):
            ticks[i] = self.strip(ticks[i], interval)
            for j in range(len(ticks[i])):
                if qwtFuzzyCompare(ticks[i][j], 0.0, stepSize) == 0:
                    ticks[i][j] = 0.0
        return ticks

    def buildMajorTicks(self, interval, stepSize):
        """
        Calculate major ticks for an interval

        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :return: Calculated ticks
        """
        numTicks = min([round(interval.width() / stepSize) + 1, 10000])
        if np.isnan(numTicks):
            numTicks = 0
        ticks = [interval.minValue()]
        for i in range(1, int(numTicks - 1)):
            ticks += [interval.minValue() + i * stepSize]
        ticks += [interval.maxValue()]
        return ticks

    def buildMinorTicks(self, ticks, maxMinorSteps, stepSize):
        """
        Calculate minor ticks for an interval

        :param list ticks: Major ticks (returned)
        :param int maxMinorSteps: Maximum number of minor steps
        :param float stepSize: Step size
        """
        minStep = qwtStepSize(stepSize, maxMinorSteps, self.base())
        if minStep == 0.0:
            return
        numTicks = int(math.ceil(abs(stepSize / minStep)) - 1)
        medIndex = -1
        if numTicks % 2:
            medIndex = numTicks / 2
        for val in ticks[QwtScaleDiv.MajorTick]:
            for k in range(numTicks):
                val += minStep
                alignedValue = val
                if qwtFuzzyCompare(val, 0.0, stepSize) == 0:
                    alignedValue = 0.0
                if k == medIndex:
                    ticks[QwtScaleDiv.MediumTick] += [alignedValue]
                else:
                    ticks[QwtScaleDiv.MinorTick] += [alignedValue]

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

        The limits of an interval are aligned that both are integer
        multiples of the step size.

        :param qwt.interval.QwtInterval interval: Interval
        :param float stepSize: Step size
        :return: Aligned interval
        """
        x1 = interval.minValue()
        x2 = interval.maxValue()
        eps = 0.000000000001
        if -DBL_MAX + stepSize <= x1:
            x = floorEps(x1, stepSize)
            if abs(x) <= eps or not qFuzzyCompare(x1, x):
                x1 = x
        if DBL_MAX - stepSize >= x2:
            x = ceilEps(x2, stepSize)
            if abs(x) <= eps or not qFuzzyCompare(x2, x):
                x2 = x
        return QwtInterval(x1, x2)
 class QwtPlotCanvas(QFrame):
    """
    Canvas of a QwtPlot.

    Canvas is the widget where all plot items are displayed

    .. seealso::

        :py:meth:`qwt.plot.QwtPlot.setCanvas()`

    Paint attributes:

        * `QwtPlotCanvas.BackingStore`:

            Paint double buffered reusing the content of the pixmap buffer
            when possible.

            Using a backing store might improve the performance significantly,
            when working with widget overlays (like rubber bands).
            Disabling the cache might improve the performance for
            incremental paints
            (using :py:class:`qwt.plot_directpainter.QwtPlotDirectPainter`).

        * `QwtPlotCanvas.Opaque`:

            Try to fill the complete contents rectangle of the plot canvas

            When using styled backgrounds Qt assumes, that the canvas doesn't
            fill its area completely (f.e because of rounded borders) and
            fills the area below the canvas. When this is done with gradients
            it might result in a serious performance bottleneck - depending on
            the size.

            When the Opaque attribute is enabled the canvas tries to
            identify the gaps with some heuristics and to fill those only.

            .. warning::

                Will not work for semitransparent backgrounds

        * `QwtPlotCanvas.HackStyledBackground`:

            Try to improve painting of styled backgrounds

            `QwtPlotCanvas` supports the box model attributes for
            customizing the layout with style sheets. Unfortunately
            the design of Qt style sheets has no concept how to
            handle backgrounds with rounded corners - beside of padding.

            When HackStyledBackground is enabled the plot canvas tries
            to separate the background from the background border
            by reverse engineering to paint the background before and
            the border after the plot items. In this order the border
            gets perfectly antialiased and you can avoid some pixel
            artifacts in the corners.

        * `QwtPlotCanvas.ImmediatePaint`:

            When ImmediatePaint is set replot() calls repaint()
            instead of update().

            .. seealso::

                :py:meth:`replot()`, :py:meth:`QWidget.repaint()`,
                :py:meth:`QWidget.update()`

    Focus indicators:

        * `QwtPlotCanvas.NoFocusIndicator`:

            Don't paint a focus indicator

        * `QwtPlotCanvas.CanvasFocusIndicator`:

            The focus is related to the complete canvas.
            Paint the focus indicator using paintFocus()

        * `QwtPlotCanvas.ItemFocusIndicator`:

            The focus is related to an item (curve, point, ...) on
            the canvas. It is up to the application to display a
            focus indication using f.e. highlighting.

    .. py:class:: QwtPlotCanvas([plot=None])

        Constructor

        :param qwt.plot.QwtPlot plot: Parent plot widget

        .. seealso::

            :py:meth:`qwt.plot.QwtPlot.setCanvas()`
    """

    # enum PaintAttribute
    BackingStore = 1
    Opaque = 2
    HackStyledBackground = 4
    ImmediatePaint = 8

    # enum FocusIndicator
    NoFocusIndicator, CanvasFocusIndicator, ItemFocusIndicator = list(range(3))

    def __init__(self, plot=None):
        super(QwtPlotCanvas, self).__init__(plot)
        self.__plot = plot
        self.setFrameStyle(QFrame.Panel | QFrame.Sunken)
        self.setLineWidth(2)
        self.__data = QwtPlotCanvas_PrivateData()
        self.setCursor(Qt.CrossCursor)
        self.setAutoFillBackground(True)
        self.setPaintAttribute(QwtPlotCanvas.BackingStore, False)
        self.setPaintAttribute(QwtPlotCanvas.Opaque, True)
        self.setPaintAttribute(QwtPlotCanvas.HackStyledBackground, True)

    def plot(self):
        """
        :return: Parent plot widget
        """
        return self.__plot

    def setPaintAttribute(self, attribute, on=True):
        """
        Changing the paint attributes

        Paint attributes:

            * `QwtPlotCanvas.BackingStore`
            * `QwtPlotCanvas.Opaque`
            * `QwtPlotCanvas.HackStyledBackground`
            * `QwtPlotCanvas.ImmediatePaint`

        :param int attribute: Paint attribute
        :param bool on: On/Off

        .. seealso::

            :py:meth:`testPaintAttribute()`, :py:meth:`backingStore()`
        """
        if bool(self.__data.paintAttributes & attribute) == on:
            return
        if on:
            self.__data.paintAttributes |= attribute
        else:
            self.__data.paintAttributes &= ~attribute
        if attribute == self.BackingStore:
            if on:
                if self.__data.backingStore is None:
                    self.__data.backingStore = QPixmap()
                if self.isVisible():
                    if QT_MAJOR_VERSION >= 5:
                        self.__data.backingStore = self.grab(self.rect())
                    else:
                        self.__data.backingStore = QPixmap.grabWidget(self, self.rect())
            else:
                self.__data.backingStore = None
        elif attribute == self.Opaque:
            if on:
                self.setAttribute(Qt.WA_OpaquePaintEvent, True)
        elif attribute in (self.HackStyledBackground, self.ImmediatePaint):
            pass

    def testPaintAttribute(self, attribute):
        """
        Test whether a paint attribute is enabled

        :param int attribute: Paint attribute
        :return: True, when attribute is enabled

        .. seealso::

            :py:meth:`setPaintAttribute()`
        """
        return self.__data.paintAttributes & attribute

    def backingStore(self):
        """
        :return: Backing store, might be None
        """
        return self.__data.backingStore

    def invalidateBackingStore(self):
        """Invalidate the internal backing store"""
        if self.__data.backingStore:
            self.__data.backingStore = QPixmap()

    def setFocusIndicator(self, focusIndicator):
        """
        Set the focus indicator

        Focus indicators:

            * `QwtPlotCanvas.NoFocusIndicator`
            * `QwtPlotCanvas.CanvasFocusIndicator`
            * `QwtPlotCanvas.ItemFocusIndicator`

        :param int focusIndicator: Focus indicator

        .. seealso::

            :py:meth:`focusIndicator()`
        """
        self.__data.focusIndicator = focusIndicator

    def focusIndicator(self):
        """
        :return: Focus indicator

        .. seealso::

            :py:meth:`setFocusIndicator()`
        """
        return self.__data.focusIndicator

    def setBorderRadius(self, radius):
        """
        Set the radius for the corners of the border frame

        :param float radius: Radius of a rounded corner

        .. seealso::

            :py:meth:`borderRadius()`
        """
        self.__data.borderRadius = max([0.0, radius])

    def borderRadius(self):
        """
        :return: Radius for the corners of the border frame

        .. seealso::

            :py:meth:`setBorderRadius()`
        """
        return self.__data.borderRadius

    def event(self, event):
        if event.type() == QEvent.PolishRequest:
            if self.testPaintAttribute(self.Opaque):
                self.setAttribute(Qt.WA_OpaquePaintEvent, True)
        if event.type() in (QEvent.PolishRequest, QEvent.StyleChange):
            self.updateStyleSheetInfo()
        return QFrame.event(self, event)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setClipRegion(event.region())
        if (
            self.testPaintAttribute(self.BackingStore)
            and self.__data.backingStore is not None
        ):
            bs = self.__data.backingStore
            if QT_MAJOR_VERSION >= 5:
                pixelRatio = bs.devicePixelRatio()
            else:
                pixelRatio = 1.0
            if bs.size() != self.size() * pixelRatio:
                bs = QwtPainter.backingStore(self, self.size())
                if self.testAttribute(Qt.WA_StyledBackground):
                    p = QPainter(bs)
                    qwtFillBackground(p, self)
                    self.drawCanvas(p, True)
                else:
                    p = QPainter()
                    if self.__data.borderRadius <= 0.0:
                        #                        print('**DEBUG: QwtPlotCanvas.paintEvent')
                        QwtPainter.fillPixmap(self, bs)
                        p.begin(bs)
                        self.drawCanvas(p, False)
                    else:
                        p.begin(bs)
                        qwtFillBackground(p, self)
                        self.drawCanvas(p, True)
                    if self.frameWidth() > 0:
                        self.drawBorder(p)
                    p.end()
            painter.drawPixmap(0, 0, self.__data.backingStore)
        else:
            if self.testAttribute(Qt.WA_StyledBackground):
                if self.testAttribute(Qt.WA_OpaquePaintEvent):
                    qwtFillBackground(painter, self)
                    self.drawCanvas(painter, True)
                else:
                    self.drawCanvas(painter, False)
            else:
                if self.testAttribute(Qt.WA_OpaquePaintEvent):
                    if self.autoFillBackground():
                        qwtFillBackground(painter, self)
                        qwtDrawBackground(painter, self)
                else:
                    if self.borderRadius() > 0.0:
                        clipPath = QPainterPath()
                        clipPath.addRect(self.rect())
                        clipPath = clipPath.subtracted(self.borderPath(self.rect()))
                        painter.save()
                        painter.setClipPath(clipPath, Qt.IntersectClip)
                        qwtFillBackground(painter, self)
                        qwtDrawBackground(painter, self)
                        painter.restore()
                self.drawCanvas(painter, False)
                if self.frameWidth() > 0:
                    self.drawBorder(painter)
        if self.hasFocus() and self.focusIndicator() == self.CanvasFocusIndicator:
            self.drawFocusIndicator(painter)

    def drawCanvas(self, painter, withBackground):
        hackStyledBackground = False
        if (
            withBackground
            and self.testAttribute(Qt.WA_StyledBackground)
            and self.testPaintAttribute(self.HackStyledBackground)
        ):
            #  Antialiasing rounded borders is done by
            #  inserting pixels with colors between the
            #  border color and the color on the canvas,
            #  When the border is painted before the plot items
            #  these colors are interpolated for the canvas
            #  and the plot items need to be clipped excluding
            #  the anialiased pixels. In situations, where
            #  the plot items fill the area at the rounded
            #  borders this is noticeable.
            #  The only way to avoid these annoying "artefacts"
            #  is to paint the border on top of the plot items.
            if (
                self.__data.styleSheet.hasBorder
                and not self.__data.styleSheet.borderPath.isEmpty()
            ):
                #  We have a border with at least one rounded corner
                hackStyledBackground = True
        if withBackground:
            painter.save()
            if self.testAttribute(Qt.WA_StyledBackground):
                if hackStyledBackground:
                    #  paint background without border
                    painter.setPen(Qt.NoPen)
                    painter.setBrush(self.__data.styleSheet.background.brush)
                    painter.setBrushOrigin(self.__data.styleSheet.background.origin)
                    painter.setClipPath(self.__data.styleSheet.borderPath)
                    painter.drawRect(self.contentsRect())
                else:
                    qwtDrawStyledBackground(self, painter)
            elif self.autoFillBackground():
                painter.setPen(Qt.NoPen)
                painter.setBrush(self.palette().brush(self.backgroundRole()))
                if self.__data.borderRadius > 0.0 and self.rect() == self.frameRect():
                    if self.frameWidth() > 0:
                        painter.setClipPath(self.borderPath(self.rect()))
                        painter.drawRect(self.rect())
                    else:
                        painter.setRenderHint(QPainter.Antialiasing, True)
                        painter.drawPath(self.borderPath(self.rect()))
                else:
                    painter.drawRect(self.rect())
            painter.restore()
        painter.save()
        if not self.__data.styleSheet.borderPath.isEmpty():
            painter.setClipPath(self.__data.styleSheet.borderPath, Qt.IntersectClip)
        else:
            if self.__data.borderRadius > 0.0:
                painter.setClipPath(self.borderPath(self.frameRect()), Qt.IntersectClip)
            else:
                #                print('**DEBUG: QwtPlotCanvas.drawCanvas')
                painter.setClipRect(self.contentsRect(), Qt.IntersectClip)
        self.plot().drawCanvas(painter)
        painter.restore()
        if withBackground and hackStyledBackground:
            #  Now paint the border on top
            opt = QStyleOptionFrame()
            opt.initFrom(self)
            self.style().drawPrimitive(QStyle.PE_Frame, opt, painter, self)

    def drawBorder(self, painter):
        """
        Draw the border of the plot canvas

        :param QPainter painter: Painter

        .. seealso::

            :py:meth:`setBorderRadius()`
        """
        if self.__data.borderRadius > 0:
            if self.frameWidth() > 0:
                QwtPainter.drawRoundedFrame(
                    painter,
                    QRectF(self.frameRect()),
                    self.__data.borderRadius,
                    self.__data.borderRadius,
                    self.palette(),
                    self.frameWidth(),
                    self.frameStyle(),
                )
        else:
            if PYQT5:
                from qtpy.QtWidgets import QStyleOptionFrame
            else:
                try:
                    from PyQt4.QtGui import QStyleOptionFrameV3 as QStyleOptionFrame
                except ImportError:
                    from PySide2.QtWidgets import QStyleOptionFrame
            opt = QStyleOptionFrame()
            opt.initFrom(self)
            frameShape = self.frameStyle() & QFrame.Shape_Mask
            frameShadow = self.frameStyle() & QFrame.Shadow_Mask
            opt.frameShape = QFrame.Shape(int(opt.frameShape) | frameShape)
            if frameShape in (
                QFrame.Box,
                QFrame.HLine,
                QFrame.VLine,
                QFrame.StyledPanel,
                QFrame.Panel,
            ):
                opt.lineWidth = self.lineWidth()
                opt.midLineWidth = self.midLineWidth()
            else:
                opt.lineWidth = self.frameWidth()
            if frameShadow == self.Sunken:
                opt.state |= QStyle.State_Sunken
            elif frameShadow == self.Raised:
                opt.state |= QStyle.State_Raised
            self.style().drawControl(QStyle.CE_ShapedFrame, opt, painter, self)

    def resizeEvent(self, event):
        QFrame.resizeEvent(self, event)
        self.updateStyleSheetInfo()

    def drawFocusIndicator(self, painter):
        """
        Draw the focus indication

        :param QPainter painter: Painter
        """
        margin = 1
        focusRect = self.contentsRect()
        focusRect.setRect(
            focusRect.x() + margin,
            focusRect.y() + margin,
            focusRect.width() - 2 * margin,
            focusRect.height() - 2 * margin,
        )
        QwtPainter.drawFocusRect(painter, self, focusRect)

    def replot(self):
        """
        Invalidate the paint cache and repaint the canvas
        """
        self.invalidateBackingStore()
        if self.testPaintAttribute(self.ImmediatePaint):
            self.repaint(self.contentsRect())
        else:
            self.update(self.contentsRect())

    def invalidatePaintCache(self):
        import warnings

        warnings.warn(
            "`invalidatePaintCache` has been removed: " "please use `replot` instead",
            RuntimeWarning,
        )
        self.replot()

    def updateStyleSheetInfo(self):
        """
        Update the cached information about the current style sheet
        """
        if not self.testAttribute(Qt.WA_StyledBackground):
            return
        recorder = QwtStyleSheetRecorder(self.size())
        painter = QPainter(recorder)
        opt = QStyleOption()
        opt.initFrom(self)
        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
        painter.end()
        self.__data.styleSheet.hasBorder = len(recorder.border.rectList) > 0
        self.__data.styleSheet.cornerRects = recorder.clipRects
        if recorder.background.path.isEmpty():
            if self.__data.styleSheet.hasBorder:
                self.__data.styleSheet.borderPath = qwtCombinePathList(
                    self.rect(), recorder.border.pathlist
                )
        else:
            self.__data.styleSheet.borderPath = recorder.background.path
            self.__data.styleSheet.background.brush = recorder.background.brush
            self.__data.styleSheet.background.origin = recorder.background.origin

    def borderPath(self, rect):
        """
        Calculate the painter path for a styled or rounded border

        When the canvas has no styled background or rounded borders
        the painter path is empty.

        :param QRect rect: Bounding rectangle of the canvas
        :return: Painter path, that can be used for clipping
        """
        if self.testAttribute(Qt.WA_StyledBackground):
            recorder = QwtStyleSheetRecorder(rect.size())
            painter = QPainter(recorder)
            opt = QStyleOption()
            opt.initFrom(self)
            opt.rect = rect
            self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
            painter.end()
            if not recorder.background.path.isEmpty():
                return recorder.background.path
            if len(recorder.border.rectList) > 0:
                return qwtCombinePathList(rect, recorder.border.pathlist)
        elif self.__data.borderRadius > 0.0:
            fw2 = self.frameWidth() * 0.5
            r = QRectF(rect).adjusted(fw2, fw2, -fw2, -fw2)
            path = QPainterPath()
            path.addRoundedRect(r, self.__data.borderRadius, self.__data.borderRadius)
            return path
        return QPainterPath()
 class QwtScaleDiv(object):
    """
    A class representing a scale division

    A Qwt scale is defined by its boundaries and 3 list
    for the positions of the major, medium and minor ticks.

    The `upperLimit()` might be smaller than the `lowerLimit()`
    to indicate inverted scales.

    Scale divisions can be calculated from a `QwtScaleEngine`.

    .. seealso::

        :py:meth:`qwt.scale_engine.QwtScaleEngine.divideScale()`,
        :py:meth:`qwt.plot.QwtPlot.setAxisScaleDiv()`

    Scale tick types:

      * `QwtScaleDiv.NoTick`: No ticks
      * `QwtScaleDiv.MinorTick`: Minor ticks
      * `QwtScaleDiv.MediumTick`: Medium ticks
      * `QwtScaleDiv.MajorTick`: Major ticks
      * `QwtScaleDiv.NTickTypes`: Number of valid tick types

    .. py:class:: QwtScaleDiv()

        Basic constructor. Lower bound = Upper bound = 0.

    .. py:class:: QwtScaleDiv(interval, ticks)
        :noindex:

        :param qwt.interval.QwtInterval interval: Interval
        :param list ticks: list of major, medium and minor ticks

    .. py:class:: QwtScaleDiv(lowerBound, upperBound)
        :noindex:

        :param float lowerBound: First boundary
        :param float upperBound: Second boundary

    .. py:class:: QwtScaleDiv(lowerBound, upperBound, ticks)
        :noindex:

        :param float lowerBound: First boundary
        :param float upperBound: Second boundary
        :param list ticks: list of major, medium and minor ticks

    .. py:class:: QwtScaleDiv(lowerBound, upperBound, minorTicks, mediumTicks, majorTicks)
        :noindex:

        :param float lowerBound: First boundary
        :param float upperBound: Second boundary
        :param list minorTicks: list of minor ticks
        :param list mediumTicks: list of medium ticks
        :param list majorTicks: list of major ticks

    .. note::

        lowerBound might be greater than upperBound for inverted scales
    """

    # enum TickType
    NoTick = -1
    MinorTick, MediumTick, MajorTick, NTickTypes = list(range(4))

    def __init__(self, *args):
        self.__ticks = None
        if len(args) == 2 and isinstance(args[1], (tuple, list)):
            interval, ticks = args
            self.__lowerBound = interval.minValue()
            self.__upperBound = interval.maxValue()
            self.__ticks = ticks[:]
        elif len(args) == 2:
            self.__lowerBound, self.__upperBound = args
        elif len(args) == 3:
            self.__lowerBound, self.__upperBound, ticks = args
            self.__ticks = ticks[:]
        elif len(args) == 5:
            (
                self.__lowerBound,
                self.__upperBound,
                minorTicks,
                mediumTicks,
                majorTicks,
            ) = args
            self.__ticks = [0] * self.NTickTypes
            self.__ticks[self.MinorTick] = minorTicks
            self.__ticks[self.MediumTick] = mediumTicks
            self.__ticks[self.MajorTick] = majorTicks
        elif len(args) == 0:
            self.__lowerBound, self.__upperBound = 0.0, 0.0
        else:
            raise TypeError(
                "%s() takes 0, 2, 3 or 5 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )

    def setInterval(self, *args):
        """
        Change the interval

        .. py:method:: setInterval(lowerBound, upperBound)
            :noindex:

            :param float lowerBound: First boundary
            :param float upperBound: Second boundary

        .. py:method:: setInterval(interval)
            :noindex:

            :param qwt.interval.QwtInterval interval: Interval

        .. note::

            lowerBound might be greater than upperBound for inverted scales
        """
        if len(args) == 2:
            self.__lowerBound, self.__upperBound = args
        elif len(args) == 1:
            (interval,) = args
            self.__lowerBound = interval.minValue()
            self.__upperBound = interval.maxValue()
        else:
            raise TypeError(
                "%s().setInterval() takes 1 or 2 argument(s) (%s "
                "given)" % (self.__class__.__name__, len(args))
            )

    def interval(self):
        """
        :return: Interval
        """
        return QwtInterval(self.__lowerBound, self.__upperBound)

    def setLowerBound(self, lowerBound):
        """
        Set the first boundary

        :param float lowerBound: First boundary

        .. seealso::

            :py:meth:`lowerBound()`, :py:meth:`setUpperBound()`
        """
        self.__lowerBound = lowerBound

    def lowerBound(self):
        """
        :return: the first boundary

        .. seealso::

            :py:meth:`upperBound()`
        """
        return self.__lowerBound

    def setUpperBound(self, upperBound):
        """
        Set the second boundary

        :param float lowerBound: Second boundary

        .. seealso::

            :py:meth:`upperBound()`, :py:meth:`setLowerBound()`
        """
        self.__upperBound = upperBound

    def upperBound(self):
        """
        :return: the second boundary

        .. seealso::

            :py:meth:`lowerBound()`
        """
        return self.__upperBound

    def range(self):
        """
        :return: upperBound() - lowerBound()
        """
        return self.__upperBound - self.__lowerBound

    def __eq__(self, other):
        if self.__ticks is None:
            return False
        if (
            self.__lowerBound != other.__lowerBound
            or self.__upperBound != other.__upperBound
        ):
            return False
        return self.__ticks == other.__ticks

    def __ne__(self, other):
        return not self.__eq__(other)

    def isEmpty(self):
        """
        Check if the scale division is empty( lowerBound() == upperBound() )
        """
        return self.__lowerBound == self.__upperBound

    def isIncreasing(self):
        """
        Check if the scale division is increasing( lowerBound() <= upperBound() )
        """
        return self.__lowerBound <= self.__upperBound

    def contains(self, value):
        """
        Return if a value is between lowerBound() and upperBound()

        :param float value: Value
        :return: True/False
        """
        min_ = min([self.__lowerBound, self.__upperBound])
        max_ = max([self.__lowerBound, self.__upperBound])
        return value >= min_ and value <= max_

    def invert(self):
        """
        Invert the scale division

        .. seealso::

            :py:meth:`inverted()`
        """
        (self.__lowerBound, self.__upperBound) = self.__upperBound, self.__lowerBound
        for index in range(self.NTickTypes):
            self.__ticks[index].reverse()

    def inverted(self):
        """
        :return: A scale division with inverted boundaries and ticks

        .. seealso::

            :py:meth:`invert()`
        """
        other = copy.deepcopy(self)
        other.invert()
        return other

    def bounded(self, lowerBound, upperBound):
        """
        Return a scale division with an interval [lowerBound, upperBound]
        where all ticks outside this interval are removed

        :param float lowerBound: First boundary
        :param float lowerBound: Second boundary
        :return: Scale division with all ticks inside of the given interval

        .. note::

            lowerBound might be greater than upperBound for inverted scales
        """
        min_ = min([self.__lowerBound, self.__upperBound])
        max_ = max([self.__lowerBound, self.__upperBound])
        sd = QwtScaleDiv()
        sd.setInterval(lowerBound, upperBound)
        for tickType in range(self.NTickTypes):
            sd.setTicks(
                tickType,
                [
                    tick
                    for tick in self.__ticks[tickType]
                    if tick >= min_ and tick <= max_
                ],
            )
        return sd

    def setTicks(self, tickType, ticks):
        """
        Assign ticks

        :param int type: MinorTick, MediumTick or MajorTick
        :param list ticks: Values of the tick positions
        """
        if tickType in range(self.NTickTypes):
            self.__ticks[tickType] = ticks

    def ticks(self, tickType):
        """
        Return a list of ticks

        :param int type: MinorTick, MediumTick or MajorTick
        :return: Tick list
        """
        if self.__ticks is not None and tickType in range(self.NTickTypes):
            return self.__ticks[tickType]
        else:
            return []
 class QwtScaleMap(object):
    """
    A scale map

    `QwtScaleMap` offers transformations from the coordinate system
    of a scale into the linear coordinate system of a paint device
    and vice versa.

    The scale and paint device intervals are both set to [0,1].

    .. py:class:: QwtScaleMap([other=None])

        Constructor (eventually, copy constructor)

        :param qwt.scale_map.QwtScaleMap other: Other scale map

    .. py:class:: QwtScaleMap(p1, p2, s1, s2)
        :noindex:

        Constructor (was provided by `PyQwt` but not by `Qwt`)

        :param int p1: First border of the paint interval
        :param int p2: Second border of the paint interval
        :param float s1: First border of the scale interval
        :param float s2: Second border of the scale interval
    """

    def __init__(self, *args):
        self.__transform = None  # QwtTransform
        self.__s1 = 0.0
        self.__s2 = 1.0
        self.__p1 = 0.0
        self.__p2 = 1.0
        other = None
        if len(args) == 1:
            (other,) = args
        elif len(args) == 4:
            p1, p2, s1, s2 = args
            self.__s1 = s1
            self.__s2 = s2
            self.__p1 = p1
            self.__p2 = p2
        elif len(args) != 0:
            raise TypeError(
                "%s() takes 0, 1, or 4 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )
        if other is None:
            self.__cnv = 1.0
            self.__ts1 = 0.0
        else:
            self.__s1 = other.__s1
            self.__s2 = other.__s2
            self.__p1 = other.__p1
            self.__p2 = other.__p2
            self.__cnv = other.__cnv
            self.__ts1 = other.__ts1
            if other.__transform:
                self.__transform = other.__transform.copy()

    def __eq__(self, other):
        return (
            self.__s1 == other.__s1
            and self.__s2 == other.__s2
            and self.__p1 == other.__p1
            and self.__p2 == other.__p2
            and self.__cnv == other.__cnv
            and self.__ts1 == other.__ts1
        )

    def s1(self):
        """
        :return: First border of the scale interval
        """
        return self.__s1

    def s2(self):
        """
        :return: Second border of the scale interval
        """
        return self.__s2

    def p1(self):
        """
        :return: First border of the paint interval
        """
        return self.__p1

    def p2(self):
        """
        :return: Second border of the paint interval
        """
        return self.__p2

    def pDist(self):
        """
        :return: `abs(p2() - p1())`
        """
        return abs(self.__p2 - self.__p1)

    def sDist(self):
        """
        :return: `abs(s2() - s1())`
        """
        return abs(self.__s2 - self.__s1)

    def transform_scalar(self, s):
        """
        Transform a point related to the scale interval into an point
        related to the interval of the paint device

        :param float s: Value relative to the coordinates of the scale
        :return: Transformed value

        .. seealso::

            :py:meth:`invTransform_scalar()`
        """
        if self.__transform:
            s = self.__transform.transform(s)
        return self.__p1 + (s - self.__ts1) * self.__cnv

    def invTransform_scalar(self, p):
        """
        Transform an paint device value into a value in the
        interval of the scale.

        :param float p: Value relative to the coordinates of the paint device
        :return: Transformed value

        .. seealso::

            :py:meth:`transform_scalar()`
        """
        if self.__cnv == 0:
            s = self.__ts1  # avoid divide by zero
        else:
            s = self.__ts1 + (p - self.__p1) / self.__cnv
        if self.__transform:
            s = self.__transform.invTransform(s)
        return s

    def isInverting(self):
        """
        :return: True, when ( p1() < p2() ) != ( s1() < s2() )
        """
        return (self.__p1 < self.__p2) != (self.__s1 < self.__s2)

    def setTransformation(self, transform):
        """
        Initialize the map with a transformation

        :param qwt.transform.QwtTransform transform: Transformation
        """
        if self.__transform != transform:
            self.__transform = transform
        self.setScaleInterval(self.__s1, self.__s2)

    def transformation(self):
        """
        :return: the transformation
        """
        return self.__transform

    def setScaleInterval(self, s1, s2):
        """
        Specify the borders of the scale interval

        :param float s1: first border
        :param float s2: second border

        .. warning::

            Scales might be aligned to transformation depending boundaries
        """
        self.__s1 = s1
        self.__s2 = s2
        if self.__transform:
            self.__s1 = self.__transform.bounded(self.__s1)
            self.__s2 = self.__transform.bounded(self.__s2)
        self.updateFactor()

    def setPaintInterval(self, p1, p2):
        """
        Specify the borders of the paint device interval

        :param float p1: first border
        :param float p2: second border
        """
        self.__p1 = p1
        self.__p2 = p2
        self.updateFactor()

    def updateFactor(self):
        self.__ts1 = self.__s1
        ts2 = self.__s2
        if self.__transform:
            self.__ts1 = self.__transform.transform(self.__ts1)
            ts2 = self.__transform.transform(ts2)
        self.__cnv = 1.0
        if self.__ts1 != ts2:
            self.__cnv = (self.__p2 - self.__p1) / (ts2 - self.__ts1)

    def transform(self, *args):
        """
        Transform a rectangle from scale to paint coordinates

        .. py:method:: transform(scalar)

            :param float scalar: Scalar

        .. py:method:: transform(xMap, yMap, rect)

            Transform a rectangle from scale to paint coordinates

            :param qwt.scale_map.QwtScaleMap xMap: X map
            :param qwt.scale_map.QwtScaleMap yMap: Y map
            :param QRectF rect: Rectangle in paint coordinates

        .. py:method:: transform(xMap, yMap, pos)

            Transform a point from scale to paint coordinates

            :param qwt.scale_map.QwtScaleMap xMap: X map
            :param qwt.scale_map.QwtScaleMap yMap: Y map
            :param QPointF pos: Position in scale coordinates

        Scalar: scalemap.transform(scalar)
        Point (QPointF): scalemap.transform(xMap, yMap, pos)
        Rectangle (QRectF): scalemap.transform(xMap, yMap, rect)

        .. seealso::

            :py:meth:`invTransform()`
        """
        if len(args) == 1:
            # Scalar transform
            return self.transform_scalar(args[0])
        elif len(args) == 3 and isinstance(args[2], QPointF):
            xMap, yMap, pos = args
            return QPointF(xMap.transform(pos.x()), yMap.transform(pos.y()))
        elif len(args) == 3 and isinstance(args[2], QRectF):
            xMap, yMap, rect = args
            x1 = xMap.transform(rect.left())
            x2 = xMap.transform(rect.right())
            y1 = yMap.transform(rect.top())
            y2 = yMap.transform(rect.bottom())
            if x2 < x1:
                x1, x2 = x2, x1
            if y2 < y1:
                y1, y2 = y2, y1
            if qwtFuzzyCompare(x1, 0.0, x2 - x1) == 0:
                x1 = 0.0
            if qwtFuzzyCompare(x2, 0.0, x2 - x1) == 0:
                x2 = 0.0
            if qwtFuzzyCompare(y1, 0.0, y2 - y1) == 0:
                y1 = 0.0
            if qwtFuzzyCompare(y2, 0.0, y2 - y1) == 0:
                y2 = 0.0
            return QRectF(x1, y1, x2 - x1 + 1, y2 - y1 + 1)
        else:
            raise TypeError(
                "%s().transform() takes 1 or 3 argument(s) (%s "
                "given)" % (self.__class__.__name__, len(args))
            )

    def invTransform(self, *args):
        """Transform from paint to scale coordinates

        Scalar: scalemap.invTransform(scalar)
        Point (QPointF): scalemap.invTransform(xMap, yMap, pos)
        Rectangle (QRectF): scalemap.invTransform(xMap, yMap, rect)
        """
        if len(args) == 1:
            # Scalar transform
            return self.invTransform_scalar(args[0])
        elif isinstance(args[2], QPointF):
            xMap, yMap, pos = args
            return QPointF(xMap.invTransform(pos.x()), yMap.invTransform(pos.y()))
        elif isinstance(args[2], QRectF):
            xMap, yMap, rect = args
            x1 = xMap.invTransform(rect.left())
            x2 = xMap.invTransform(rect.right() - 1)
            y1 = yMap.invTransform(rect.top())
            y2 = yMap.invTransform(rect.bottom() - 1)
            r = QRectF(x1, y1, x2 - x1, y2 - y1)
            return r.normalized()
 class QwtGraphic(QwtNullPaintDevice):
    """
    A paint device for scalable graphics

    `QwtGraphic` is the representation of a graphic that is tailored for
    scalability. Like `QPicture` it will be initialized by `QPainter`
    operations and can be replayed later to any target paint device.

    While the usual image representations `QImage` and `QPixmap` are not
    scalable `Qt` offers two paint devices, that might be candidates
    for representing a vector graphic:

        - `QPicture`:

          Unfortunately `QPicture` had been forgotten, when Qt4
          introduced floating point based render engines. Its API
          is still on integers, what make it unusable for proper scaling.

        - `QSvgRenderer`, `QSvgGenerator`:

          Unfortunately `QSvgRenderer` hides to much information about
          its nodes in internal APIs, that are necessary for proper
          layout calculations. Also it is derived from `QObject` and
          can't be copied like `QImage`/`QPixmap`.

    `QwtGraphic` maps all scalable drawing primitives to a `QPainterPath`
    and stores them together with the painter state changes
    ( pen, brush, transformation ... ) in a list of `QwtPaintCommands`.
    For being a complete `QPaintDevice` it also stores pixmaps or images,
    what is somehow against the idea of the class, because these objects
    can't be scaled without a loss in quality.

    The main issue about scaling a `QwtGraphic` object are the pens used for
    drawing the outlines of the painter paths. While non cosmetic pens
    ( `QPen.isCosmetic()` ) are scaled with the same ratio as the path,
    cosmetic pens have a fixed width. A graphic might have paths with
    different pens - cosmetic and non-cosmetic.

    `QwtGraphic` caches 2 different rectangles:

        - control point rectangle:

          The control point rectangle is the bounding rectangle of all
          control point rectangles of the painter paths, or the target
          rectangle of the pixmaps/images.

        - bounding rectangle:

          The bounding rectangle extends the control point rectangle by
          what is needed for rendering the outline with an unscaled pen.

    Because the offset for drawing the outline depends on the shape
    of the painter path ( the peak of a triangle is different than the flat side )
    scaling with a fixed aspect ratio always needs to be calculated from the
    control point rectangle.

    .. py:class:: QwtGraphic()

        Initializes a null graphic

    .. py:class:: QwtGraphic(other)
        :noindex:

        Copy constructor

        :param qwt.graphic.QwtGraphic other: Source
    """

    # enum RenderHint
    RenderPensUnscaled = 0x1

    def __init__(self, *args):
        QwtNullPaintDevice.__init__(self)
        if len(args) == 0:
            self.setMode(QwtNullPaintDevice.PathMode)
            self.__data = QwtGraphic_PrivateData()
        elif len(args) == 1:
            (other,) = args
            self.setMode(other.mode())
            self.__data = other.__data
        else:
            raise TypeError(
                "%s() takes 0 or 1 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )

    def reset(self):
        """Clear all stored commands"""
        self.__data.commands = []
        self.__data.pathInfos = []
        self.__data.boundingRect = QRectF(0.0, 0.0, -1.0, -1.0)
        self.__data.pointRect = QRectF(0.0, 0.0, -1.0, -1.0)
        self.__data.defaultSize = QSizeF()

    def isNull(self):
        """Return True, when no painter commands have been stored"""
        return len(self.__data.commands) == 0

    def isEmpty(self):
        """Return True, when the bounding rectangle is empty"""
        return self.__data.boundingRect.isEmpty()

    def setRenderHint(self, hint, on=True):
        """Toggle an render hint"""
        if on:
            self.__data.renderHints |= hint
        else:
            self.__data.renderHints &= ~hint

    def testRenderHint(self, hint):
        """Test a render hint"""
        return bool(self.__data.renderHints & hint)

    def boundingRect(self):
        """
        The bounding rectangle is the :py:meth:`controlPointRect`
        extended by the areas needed for rendering the outlines
        with unscaled pens.

        :return: Bounding rectangle of the graphic

        .. seealso::

            :py:meth:`controlPointRect`, :py:meth:`scaledBoundingRect`
        """
        if self.__data.boundingRect.width() < 0:
            return QRectF()
        return self.__data.boundingRect

    def controlPointRect(self):
        """
        The control point rectangle is the bounding rectangle
        of all control points of the paths and the target
        rectangles of the images/pixmaps.

        :return: Control point rectangle

        .. seealso::

            :py:meth:`boundingRect()`, :py:meth:`scaledBoundingRect()`
        """
        if self.__data.pointRect.width() < 0:
            return QRectF()
        return self.__data.pointRect

    def scaledBoundingRect(self, sx, sy):
        """
        Calculate the target rectangle for scaling the graphic

        :param float sx: Horizontal scaling factor
        :param float sy: Vertical scaling factor
        :return: Scaled bounding rectangle

        .. note::

            In case of paths that are painted with a cosmetic pen
            (see :py:meth:`QPen.isCosmetic()`) the target rectangle is
            different to multiplying the bounding rectangle.

        .. seealso::

            :py:meth:`boundingRect()`, :py:meth:`controlPointRect()`
        """
        if sx == 1.0 and sy == 1.0:
            return self.__data.boundingRect
        transform = QTransform()
        transform.scale(sx, sy)
        rect = transform.mapRect(self.__data.pointRect)
        for pathInfo in self.__data.pathInfos:
            rect |= pathInfo.scaledBoundingRect(
                sx, sy, not bool(self.__data.renderHints & self.RenderPensUnscaled)
            )
        return rect

    def sizeMetrics(self):
        """Return Ceiled :py:meth:`defaultSize()`"""
        sz = self.defaultSize()
        return QSize(np.ceil(sz.width()), np.ceil(sz.height()))

    def setDefaultSize(self, size):
        """
        The default size is used in all methods rendering the graphic,
        where no size is explicitly specified. Assigning an empty size
        means, that the default size will be calculated from the bounding
        rectangle.

        :param QSizeF size: Default size

        .. seealso::

            :py:meth:`defaultSize()`, :py:meth:`boundingRect()`
        """
        w = max([0.0, size.width()])
        h = max([0.0, size.height()])
        self.__data.defaultSize = QSizeF(w, h)

    def defaultSize(self):
        """
        When a non empty size has been assigned by setDefaultSize() this
        size will be returned. Otherwise the default size is the size
        of the bounding rectangle.

        The default size is used in all methods rendering the graphic,
        where no size is explicitly specified.

        :return: Default size

        .. seealso::

            :py:meth:`setDefaultSize()`, :py:meth:`boundingRect()`
        """
        if not self.__data.defaultSize.isEmpty():
            return self.__data.defaultSize
        return self.boundingRect().size()

    def render(self, *args):
        """
        .. py:method:: render(painter)
            :noindex:

            Replay all recorded painter commands

            :param QPainter painter: Qt painter

        .. py:method:: render(painter, size, aspectRatioMode)
            :noindex:

            Replay all recorded painter commands

            The graphic is scaled to fit into the rectangle
            of the given size starting at ( 0, 0 ).

            :param QPainter painter: Qt painter
            :param QSizeF size: Size for the scaled graphic
            :param Qt.AspectRatioMode aspectRatioMode: Mode how to scale

        .. py:method:: render(painter, rect, aspectRatioMode)
            :noindex:

            Replay all recorded painter commands

            The graphic is scaled to fit into the given rectangle

            :param QPainter painter: Qt painter
            :param QRectF rect: Rectangle for the scaled graphic
            :param Qt.AspectRatioMode aspectRatioMode: Mode how to scale

        .. py:method:: render(painter, pos, aspectRatioMode)
            :noindex:

            Replay all recorded painter commands

            The graphic is scaled to the :py:meth:`defaultSize()` and aligned
            to a position.

            :param QPainter painter: Qt painter
            :param QPointF pos: Reference point, where to render
            :param Qt.AspectRatioMode aspectRatioMode: Mode how to scale
        """
        if len(args) == 1:
            (painter,) = args
            if self.isNull():
                return
            transform = painter.transform()
            painter.save()
            for command in self.__data.commands:
                qwtExecCommand(
                    painter,
                    command,
                    self.__data.renderHints,
                    transform,
                    self.__data.initialTransform,
                )
            painter.restore()
        elif len(args) in (2, 3) and isinstance(args[1], QSizeF):
            painter, size = args[:2]
            aspectRatioMode = Qt.IgnoreAspectRatio
            if len(args) == 3:
                aspectRatioMode = args[-1]
            r = QRectF(0.0, 0.0, size.width(), size.height())
            self.render(painter, r, aspectRatioMode)
        elif len(args) in (2, 3) and isinstance(args[1], QRectF):
            painter, rect = args[:2]
            aspectRatioMode = Qt.IgnoreAspectRatio
            if len(args) == 3:
                aspectRatioMode = args[-1]
            if self.isEmpty() or rect.isEmpty():
                return
            sx = 1.0
            sy = 1.0
            if self.__data.pointRect.width() > 0.0:
                sx = rect.width() / self.__data.pointRect.width()
            if self.__data.pointRect.height() > 0.0:
                sy = rect.height() / self.__data.pointRect.height()
            scalePens = not bool(self.__data.renderHints & self.RenderPensUnscaled)
            for info in self.__data.pathInfos:
                ssx = info.scaleFactorX(self.__data.pointRect, rect, scalePens)
                if ssx > 0.0:
                    sx = min([sx, ssx])
                ssy = info.scaleFactorY(self.__data.pointRect, rect, scalePens)
                if ssy > 0.0:
                    sy = min([sy, ssy])
            if aspectRatioMode == Qt.KeepAspectRatio:
                s = min([sx, sy])
                sx = s
                sy = s
            elif aspectRatioMode == Qt.KeepAspectRatioByExpanding:
                s = max([sx, sy])
                sx = s
                sy = s
            tr = QTransform()
            tr.translate(
                rect.center().x() - 0.5 * sx * self.__data.pointRect.width(),
                rect.center().y() - 0.5 * sy * self.__data.pointRect.height(),
            )
            tr.scale(sx, sy)
            tr.translate(-self.__data.pointRect.x(), -self.__data.pointRect.y())
            transform = painter.transform()
            if not scalePens and transform.isScaling():
                #  we don't want to scale pens according to sx/sy,
                #  but we want to apply the scaling from the
                #  painter transformation later
                self.__data.initialTransform = QTransform()
                self.__data.initialTransform.scale(transform.m11(), transform.m22())
            painter.setTransform(tr, True)
            self.render(painter)
            painter.setTransform(transform)
            self.__data.initialTransform = None
        elif len(args) in (2, 3) and isinstance(args[1], QPointF):
            painter, pos = args[:2]
            alignment = Qt.AlignTop | Qt.AlignLeft
            if len(args) == 3:
                alignment = args[-1]
            r = QRectF(pos, self.defaultSize())
            if alignment & Qt.AlignLeft:
                r.moveLeft(pos.x())
            elif alignment & Qt.AlignHCenter:
                r.moveCenter(QPointF(pos.x(), r.center().y()))
            elif alignment & Qt.AlignRight:
                r.moveRight(pos.x())
            if alignment & Qt.AlignTop:
                r.moveTop(pos.y())
            elif alignment & Qt.AlignVCenter:
                r.moveCenter(QPointF(r.center().x(), pos.y()))
            elif alignment & Qt.AlignBottom:
                r.moveBottom(pos.y())
            self.render(painter, r)
        else:
            raise TypeError(
                "%s().render() takes 1, 2 or 3 argument(s) (%s "
                "given)" % (self.__class__.__name__, len(args))
            )

    def toPixmap(self, *args):
        """
        Convert the graphic to a `QPixmap`

        All pixels of the pixmap get initialized by `Qt.transparent`
        before the graphic is scaled and rendered on it.

        The size of the pixmap is the default size ( ceiled to integers )
        of the graphic.

        :return: The graphic as pixmap in default size

        .. seealso::

            :py:meth:`defaultSize()`, :py:meth:`toImage()`, :py:meth:`render()`
        """
        if len(args) == 0:
            if self.isNull():
                return QPixmap()
            sz = self.defaultSize()
            w = np.ceil(sz.width())
            h = np.ceil(sz.height())
            pixmap = QPixmap(w, h)
            pixmap.fill(Qt.transparent)
            r = QRectF(0.0, 0.0, sz.width(), sz.height())
            painter = QPainter(pixmap)
            self.render(painter, r, Qt.KeepAspectRatio)
            painter.end()
            return pixmap
        elif len(args) in (1, 2):
            size = args[0]
            aspectRatioMode = Qt.IgnoreAspectRatio
            if len(args) == 2:
                aspectRatioMode = args[-1]
            pixmap = QPixmap(size)
            pixmap.fill(Qt.transparent)
            r = QRect(0, 0, size.width(), size.height())
            painter = QPainter(pixmap)
            self.render(painter, r, aspectRatioMode)
            painter.end()
            return pixmap

    def toImage(self, *args):
        """
        .. py:method:: toImage()
            :noindex:

            Convert the graphic to a `QImage`

            All pixels of the image get initialized by 0 ( transparent )
            before the graphic is scaled and rendered on it.

            The format of the image is `QImage.Format_ARGB32_Premultiplied`.

            The size of the image is the default size ( ceiled to integers )
            of the graphic.

            :return: The graphic as image in default size

        .. py:method:: toImage(size, [aspectRatioMode=Qt.IgnoreAspectRatio])
            :noindex:

            Convert the graphic to a `QImage`

            All pixels of the image get initialized by 0 ( transparent )
            before the graphic is scaled and rendered on it.

            The format of the image is `QImage.Format_ARGB32_Premultiplied`.

            :param QSize size: Size of the image
            :param `Qt.AspectRatioMode` aspectRatioMode: Aspect ratio how to scale the graphic
            :return: The graphic as image

        .. seealso::

            :py:meth:`toPixmap()`, :py:meth:`render()`
        """
        if len(args) == 0:
            if self.isNull():
                return QImage()
            sz = self.defaultSize()
            w = np.ceil(sz.width())
            h = np.ceil(sz.height())
            image = QImage(w, h, QImage.Format_ARGB32)
            image.fill(0)
            r = QRect(0, 0, sz.width(), sz.height())
            painter = QPainter(image)
            self.render(painter, r, Qt.KeepAspectRatio)
            painter.end()
            return image
        elif len(args) in (1, 2):
            size = args[0]
            aspectRatioMode = Qt.IgnoreAspectRatio
            if len(args) == 2:
                aspectRatioMode = args[-1]
            image = QImage(size, QImage.Format_ARGB32_Premultiplied)
            image.fill(0)
            r = QRect(0, 0, size.width(), size.height())
            painter = QPainter(image)
            self.render(painter, r, aspectRatioMode)
            return image

    def drawPath(self, path):
        """
        Store a path command in the command list

        :param QPainterPath path: Painter path

        .. seealso::

            :py:meth:`QPaintEngine.drawPath()`
        """
        painter = self.paintEngine().painter()
        if painter is None:
            return
        self.__data.commands += [QwtPainterCommand(path)]
        if not path.isEmpty():
            scaledPath = painter.transform().map(path)
            pointRect = scaledPath.boundingRect()
            boundingRect = QRectF(pointRect)
            if (
                painter.pen().style() != Qt.NoPen
                and painter.pen().brush().style() != Qt.NoBrush
            ):
                boundingRect = qwtStrokedPathRect(painter, path)
            self.updateControlPointRect(pointRect)
            self.updateBoundingRect(boundingRect)
            self.__data.pathInfos += [
                PathInfo(pointRect, boundingRect, qwtHasScalablePen(painter))
            ]

    def drawPixmap(self, rect, pixmap, subRect):
        """
        Store a pixmap command in the command list

        :param QRectF rect: target rectangle
        :param QPixmap pixmap: Pixmap to be painted
        :param QRectF subRect: Reactangle of the pixmap to be painted

        .. seealso::

            :py:meth:`QPaintEngine.drawPixmap()`
        """
        painter = self.paintEngine().painter()
        if painter is None:
            return
        self.__data.commands += [QwtPainterCommand(rect, pixmap, subRect)]
        r = painter.transform().mapRect(rect)
        self.updateControlPointRect(r)
        self.updateBoundingRect(r)

    def drawImage(self, rect, image, subRect, flags):
        """
        Store a image command in the command list

        :param QRectF rect: target rectangle
        :param QImage image: Pixmap to be painted
        :param QRectF subRect: Reactangle of the pixmap to be painted
        :param Qt.ImageConversionFlags flags: Pixmap to be painted

        .. seealso::

            :py:meth:`QPaintEngine.drawImage()`
        """
        painter = self.paintEngine().painter()
        if painter is None:
            return
        self.__data.commands += [QwtPainterCommand(rect, image, subRect, flags)]
        r = painter.transform().mapRect(rect)
        self.updateControlPointRect(r)
        self.updateBoundingRect(r)

    def updateState(self, state):
        """
        Store a state command in the command list

        :param QPaintEngineState state: State to be stored

        .. seealso::

            :py:meth:`QPaintEngine.updateState()`
        """
        # XXX: shall we call the parent's implementation of updateState?
        self.__data.commands += [QwtPainterCommand(state)]

    def updateBoundingRect(self, rect):
        br = QRectF(rect)
        painter = self.paintEngine().painter()
        if painter and painter.hasClipping():
            cr = painter.clipRegion().boundingRect()
            cr = painter.transform().mapRect(cr)
            br &= cr
        if self.__data.boundingRect.width() < 0:
            self.__data.boundingRect = br
        else:
            self.__data.boundingRect |= br

    def updateControlPointRect(self, rect):
        if self.__data.pointRect.width() < 0.0:
            self.__data.pointRect = rect
        else:
            self.__data.pointRect |= rect

    def commands(self):
        return self.__data.commands

    def setCommands(self, commands):
        self.reset()
        painter = QPainter(self)
        for cmd in commands:
            qwtExecCommand(painter, cmd, 0, QTransform(), None)
        painter.end()
 class QwtLegendData(object):
    """
    Attributes of an entry on a legend

    `QwtLegendData` is an abstract container ( like `QAbstractModel` )
    to exchange attributes, that are only known between to
    the plot item and the legend.

    By overloading `QwtPlotItem.legendData()` any other set of attributes
    could be used, that can be handled by a modified ( or completely
    different ) implementation of a legend.

    .. seealso::

        :py:class:`qwt.legend.QwtLegend`

    .. note::

        The stockchart example implements a legend as a tree
        with checkable items
    """

    # enum Mode
    ReadOnly, Clickable, Checkable = list(range(3))

    # enum Role
    ModeRole, TitleRole, IconRole = list(range(3))
    UserRole = 32

    def __init__(self):
        self.__map = {}

    def setValues(self, map_):
        """
        Set the legend attributes

        :param dict map_: Values

        .. seealso::

            :py:meth:`values()`
        """
        self.__map = map_

    def values(self):
        """
        :return: Legend attributes

        .. seealso::

            :py:meth:`setValues()`
        """
        return self.__map

    def hasRole(self, role):
        """
        :param int role: Attribute role
        :return: True, when the internal map has an entry for role
        """
        return role in self.__map

    def setValue(self, role, data):
        """
        Set an attribute value

        :param int role: Attribute role
        :param QVariant data: Attribute value

        .. seealso::

            :py:meth:`value()`
        """
        self.__map[role] = data

    def value(self, role):
        """
        :param int role: Attribute role
        :return: Attribute value for a specific role

        .. seealso::

            :py:meth:`setValue()`
        """
        return self.__map.get(role)

    def isValid(self):
        """
        :return: True, when the internal map is empty
        """
        return len(self.__map) != 0

    def title(self):
        """
        :return: Value of the TitleRole attribute
        """
        titleValue = self.value(QwtLegendData.TitleRole)
        if isinstance(titleValue, QwtText):
            text = titleValue
        else:
            text = QwtText(titleValue)
        return text

    def icon(self):
        """
        :return: Value of the IconRole attribute
        """
        return self.value(QwtLegendData.IconRole)

    def mode(self):
        """
        :return: Value of the ModeRole attribute
        """
        modeValue = self.value(QwtLegendData.ModeRole)
        if isinstance(modeValue, int):
            return modeValue
        return QwtLegendData.ReadOnly
 class QwtInterval(object):
    """
    A class representing an interval

    The interval is represented by 2 doubles, the lower and the upper limit.

    .. py:class:: QwtInterval(minValue=0., maxValue=-1., borderFlags=None)

        Build an interval with from min/max values

        :param float minValue: Minimum value
        :param float maxValue: Maximum value
        :param int borderFlags: Include/Exclude borders
    """

    # enum BorderFlag
    IncludeBorders = 0x00
    ExcludeMinimum = 0x01
    ExcludeMaximum = 0x02
    ExcludeBorders = ExcludeMinimum | ExcludeMaximum

    def __init__(self, minValue=0.0, maxValue=-1.0, borderFlags=None):
        assert not isinstance(minValue, QwtInterval)
        assert not isinstance(maxValue, QwtInterval)
        self.__minValue = None
        self.__maxValue = None
        self.__borderFlags = None
        self.setInterval(minValue, maxValue, borderFlags)

    def setInterval(self, minValue, maxValue, borderFlags=None):
        """
        Assign the limits of the interval

        :param float minValue: Minimum value
        :param float maxValue: Maximum value
        :param int borderFlags: Include/Exclude borders
        """
        self.__minValue = float(minValue)  # avoid overflows with NumPy scalars
        self.__maxValue = float(maxValue)  # avoid overflows with NumPy scalars
        if borderFlags is None:
            self.__borderFlags = self.IncludeBorders
        else:
            self.__borderFlags = borderFlags

    def setBorderFlags(self, borderFlags):
        """
        Change the border flags

        :param int borderFlags: Include/Exclude borders

        .. seealso::

            :py:meth:`borderFlags()`
        """
        self.__borderFlags = borderFlags

    def borderFlags(self):
        """
        :return: Border flags

        .. seealso::

            :py:meth:`setBorderFlags()`
        """
        return self.__borderFlags

    def setMinValue(self, minValue):
        """
        Assign the lower limit of the interval

        :param float minValue: Minimum value
        """
        self.__minValue = float(minValue)  # avoid overflows with NumPy scalars

    def setMaxValue(self, maxValue):
        """
        Assign the upper limit of the interval

        :param float maxValue: Maximum value
        """
        self.__maxValue = float(maxValue)  # avoid overflows with NumPy scalars

    def minValue(self):
        """
        :return: Lower limit of the interval
        """
        return self.__minValue

    def maxValue(self):
        """
        :return: Upper limit of the interval
        """
        return self.__maxValue

    def isValid(self):
        """
        A interval is valid when minValue() <= maxValue().
        In case of `QwtInterval.ExcludeBorders` it is true
        when minValue() < maxValue()

        :return: True, when the interval is valid
        """
        if (self.__borderFlags & self.ExcludeBorders) == 0:
            return self.__minValue <= self.__maxValue
        else:
            return self.__minValue < self.__maxValue

    def width(self):
        """
        The width of invalid intervals is 0.0, otherwise the result is
        maxValue() - minValue().

        :return: the width of an interval
        """
        if self.isValid():
            return self.__maxValue - self.__minValue
        else:
            return 0.0

    def __and__(self, other):
        return self.intersect(other)

    def __iand__(self, other):
        self = self & other
        return self

    def __or__(self, other):
        if isinstance(other, QwtInterval):
            return self.unite(other)
        else:
            return self.extend(other)

    def __ior__(self, other):
        self = self | other
        return self

    def __eq__(self, other):
        return (
            self.__minValue == other.__minValue
            and self.__maxValue == other.__maxValue
            and self.__borderFlags == other.__borderFlags
        )

    def __ne__(self, other):
        return not self.__eq__(other)

    def isNull(self):
        """
        :return: true, if isValid() && (minValue() >= maxValue())
        """
        return self.isValid() and self.__minValue >= self.__maxValue

    def invalidate(self):
        """
        The limits are set to interval [0.0, -1.0]

        .. seealso::

            :py:meth:`isValid()`
        """
        self.__minValue = 0.0
        self.__maxValue = -1.0

    def normalized(self):
        """
        Normalize the limits of the interval

        If maxValue() < minValue() the limits will be inverted.

        :return: Normalized interval

        .. seealso::

            :py:meth:`isValid()`, :py:meth:`inverted()`
        """
        if self.__minValue > self.__maxValue:
            return self.inverted()
        elif (
            self.__minValue == self.__maxValue
            and self.__borderFlags == self.ExcludeMinimum
        ):
            return self.inverted()
        else:
            return self

    def inverted(self):
        """
        Invert the limits of the interval

        :return: Inverted interval

        .. seealso::

            :py:meth:`normalized()`
        """
        borderFlags = self.IncludeBorders
        if self.__borderFlags & self.ExcludeMinimum:
            borderFlags |= self.ExcludeMaximum
        if self.__borderFlags & self.ExcludeMaximum:
            borderFlags |= self.ExcludeMinimum
        return QwtInterval(self.__maxValue, self.__minValue, borderFlags)

    def contains(self, value):
        """
        Test if a value is inside an interval

        :param float value: Value
        :return: true, if value >= minValue() && value <= maxValue()
        """
        if not self.isValid():
            return False
        elif value < self.__minValue or value > self.__maxValue:
            return False
        elif value == self.__minValue and self.__borderFlags & self.ExcludeMinimum:
            return False
        elif value == self.__maxValue and self.__borderFlags & self.ExcludeMaximum:
            return False
        else:
            return True

    def unite(self, other):
        """
        Unite two intervals

        :param qwt.interval.QwtInterval other: other interval to united with
        :return: united interval
        """
        if not self.isValid():
            if not other.isValid():
                return QwtInterval()
            else:
                return other
        elif not other.isValid():
            return self

        united = QwtInterval()
        flags = self.IncludeBorders

        # minimum
        if self.__minValue < other.minValue():
            united.setMinValue(self.__minValue)
            flags &= self.__borderFlags & self.ExcludeMinimum
        elif other.minValue() < self.__minValue:
            united.setMinValue(other.minValue())
            flags &= other.borderFlags() & self.ExcludeMinimum
        else:
            united.setMinValue(self.__minValue)
            flags &= (self.__borderFlags & other.borderFlags()) & self.ExcludeMinimum

        # maximum
        if self.__maxValue > other.maxValue():
            united.setMaxValue(self.__maxValue)
            flags &= self.__borderFlags & self.ExcludeMaximum
        elif other.maxValue() > self.__maxValue:
            united.setMaxValue(other.maxValue())
            flags &= other.borderFlags() & self.ExcludeMaximum
        else:
            united.setMaxValue(self.__maxValue)
            flags &= self.__borderFlags & other.borderFlags() & self.ExcludeMaximum

        united.setBorderFlags(flags)
        return united

    def intersect(self, other):
        """
        Intersect two intervals

        :param qwt.interval.QwtInterval other: other interval to intersect with
        :return: intersected interval
        """
        if not other.isValid() or not self.isValid():
            return QwtInterval()

        i1 = self
        i2 = other

        if i1.minValue() > i2.minValue():
            i1, i2 = i2, i1
        elif i1.minValue() == i2.minValue():
            if i1.borderFlags() & self.ExcludeMinimum:
                i1, i2 = i2, i1

        if i1.maxValue() < i2.maxValue():
            return QwtInterval()

        if i1.maxValue() == i2.minValue():
            if (
                i1.borderFlags() & self.ExcludeMaximum
                or i2.borderFlags() & self.ExcludeMinimum
            ):
                return QwtInterval()

        intersected = QwtInterval()
        flags = self.IncludeBorders

        intersected.setMinValue(i2.minValue())
        flags |= i2.borderFlags() & self.ExcludeMinimum

        if i1.maxValue() < i2.maxValue():
            intersected.setMaxValue(i1.maxValue())
            flags |= i1.borderFlags() & self.ExcludeMaximum
        elif i2.maxValue() < i1.maxValue():
            intersected.setMaxValue(i2.maxValue())
            flags |= i2.borderFlags() & self.ExcludeMaximum
        else:  # i1.maxValue() == i2.maxValue()
            intersected.setMaxValue(i1.maxValue())
            flags |= i1.borderFlags() & i2.borderFlags() & self.ExcludeMaximum

        intersected.setBorderFlags(flags)
        return intersected

    def intersects(self, other):
        """
        Test if two intervals overlap

        :param qwt.interval.QwtInterval other: other interval
        :return: True, when the intervals are intersecting
        """
        if not other.isValid() or not self.isValid():
            return False

        i1 = self
        i2 = other

        if i1.minValue() > i2.minValue():
            i1, i2 = i2, i1
        elif i1.minValue() == i2.minValue() and i1.borderFlags() & self.ExcludeMinimum:
            i1, i2 = i2, i1

        if i1.maxValue() > i2.minValue():
            return True
        elif i1.maxValue() == i2.minValue():
            return (
                i1.borderFlags() & self.ExcludeMaximum
                and i2.borderFlags() & self.ExcludeMinimum
            )
        return False

    def symmetrize(self, value):
        """
        Adjust the limit that is closer to value, so that value becomes
        the center of the interval.

        :param float value: Center
        :return: Interval with value as center
        """
        if not self.isValid():
            return self
        delta = max([abs(value - self.__maxValue), abs(value - self.__minValue)])
        return QwtInterval(value - delta, value + delta)

    def limited(self, lowerBound, upperBound):
        """
        Limit the interval, keeping the border modes

        :param float lowerBound: Lower limit
        :param float upperBound: Upper limit
        :return: Limited interval
        """
        if not self.isValid() or lowerBound > upperBound:
            return QwtInterval()
        minValue = max([self.__minValue, lowerBound])
        minValue = min([minValue, upperBound])
        maxValue = max([self.__maxValue, lowerBound])
        maxValue = min([maxValue, upperBound])
        return QwtInterval(minValue, maxValue, self.__borderFlags)

    def extend(self, value):
        """
        Extend the interval

        If value is below minValue(), value becomes the lower limit.
        If value is above maxValue(), value becomes the upper limit.

        extend() has no effect for invalid intervals

        :param float value: Value
        :return: extended interval
        """
        if not self.isValid():
            return self
        return QwtInterval(min([value, self.__minValue]), max([value, self.__maxValue]))

from qtpy.QtWidgets import (
    QWidget,
    QSizePolicy,
    QFrame,
    QApplication,
)
from qtpy.QtGui import QFont, QPainter, QPalette, QColor, QBrush
from qtpy.QtCore import Qt, Signal, QEvent, QSize, QRectF
from qwt.text import QwtText, QwtTextLabel
from qwt.scale_widget import QwtScaleWidget
from qwt.scale_draw import QwtScaleDraw
from qwt.scale_engine import QwtLinearScaleEngine
from qwt.plot_canvas import QwtPlotCanvas
from qwt.scale_div import QwtScaleDiv
from qwt.scale_map import QwtScaleMap
from qwt.graphic import QwtGraphic
from qwt.legend import QwtLegendData
from qwt.interval import QwtInterval
        from qwt.plot_layout import QwtPlotLayout
        from qwt.plot_renderer import QwtPlotRenderer
        from qwt.plot_renderer import QwtPlotRenderer
import numpy as np
        import warnings

        :type title: str or qwt.text.QwtText

        .. seealso::

            :py:meth:`title()`
        """
        current_title = self.__data.titleLabel.text()
        if isinstance(title, QwtText) and current_title == title:
            return
        elif not isinstance(title, QwtText) and current_title.text() == title:
            return
        self.__data.titleLabel.setText(title)
        self.updateLayout()

    def title(self):
        """
        :return: Title of the plot

        .. seealso::

            :py:meth:`setTitle()`
        """
        return self.__data.titleLabel.text()

    def titleLabel(self):
        """
        :return: Title label widget.
        """
        return self.__data.titleLabel

    def setFooter(self, text):
        """
        Change the text the footer

        :param text: New text of the footer
        :type text: str or qwt.text.QwtText

        .. seealso::

            :py:meth:`footer()`
        """
        current_footer = self.__data.footerLabel.text()
        if isinstance(text, QwtText) and current_footer == text:
            return
        elif not isinstance(text, QwtText) and current_footer.text() == text:
            return
        self.__data.footerLabel.setText(text)
        self.updateLayout()

    def footer(self):
        """
        :return: Text of the footer

        .. seealso::

            :py:meth:`setFooter()`
        """
        return self.__data.footerLabel.text()

    def footerLabel(self):
        """
        :return: Footer label widget.
        """
        return self.__data.footerLabel

    def setPlotLayout(self, layout):
        """
        Assign a new plot layout

        :param layout: Layout
        :type layout: qwt.plot_layout.QwtPlotLayout

        .. seealso::

            :py:meth:`plotLayout()`
        """
        if layout != self.__data.layout:
            self.__data.layout = layout
            self.updateLayout()

    def plotLayout(self):
        """
        :return: the plot's layout

        .. seealso::

            :py:meth:`setPlotLayout()`
        """
        return self.__data.layout

    def legend(self):
        """
        :return: the plot's legend

        .. seealso::

            :py:meth:`insertLegend()`
        """
        return self.__data.legend

    def canvas(self):
        """
        :return: the plot's canvas
        """
        return self.__data.canvas

    def sizeHint(self):
        """
        :return: Size hint for the plot widget

        .. seealso::

            :py:meth:`minimumSizeHint()`
        """
        dw = dh = 0
        for axisId in self.AXES:
            if self.axisEnabled(axisId):
                niceDist = 40
                scaleWidget = self.axisWidget(axisId)
                scaleDiv = scaleWidget.scaleDraw().scaleDiv()

                majCnt = len(scaleDiv.ticks(QwtScaleDiv.MajorTick))
