#/*##########################################################################
#
# The PyMca X-Ray Fluorescence Toolkit
#
# Copyright (c) 2004-2024 European Synchrotron Radiation Facility
#
# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
# the ESRF.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
#############################################################################*/
__author__ = "V.A. Sole - ESRF"
__contact__ = "sole@esrf.fr"
__license__ = "MIT"
__copyright__ = "European Synchrotron Radiation Facility, Grenoble, France"
__doc__ = """
Matplotlib Plot backend.
"""
from matplotlib import cbook
import matplotlib
# blitting enabled by default
# it provides faster response at the cost of missing minor updates
# during movement (only the bounding box of the moving object is updated)
# For instance, when moving a marker, the label is not updated during the
# movement.
BLITTING = True
import numpy
from numpy import vstack as numpyvstack
# Problem on debian6 numpy version 1.4.1 unsigned longs give infinity
#from numpy import nanmax, nanmin
def nanmax(x):
    x = numpy.asarray(x)
    x = x[numpy.isfinite(x)]
    if len(x):
        return x.max()
    else:
        return 0

def nanmin(x):
    x = numpy.asarray(x)
    x = x[numpy.isfinite(x)]
    if len(x):
        return x.min()
    else:
        return 0

import sys
import types
# This should be independent of Qt
TK = False
if ("tk" in sys.argv) or ("Tkinter" in sys.modules) or ("tkinter" in sys.modules):
    TK = True
if TK and ("PyQt5.QtCore" not in sys.modules) and ("PyQt6.QtCore" not in sys.modules) and \
    ("PySide6.QtCore" not in sys.modules) and ("PySide2.QtCore" not in sys.modules):
    import tkinter as Tk
elif 'PySide2.QtCore' in sys.modules:
    from PySide2 import QtCore, QtGui, QtWidgets
    QtGui.QApplication = QtWidgets.QApplication
elif 'PyQt5.QtCore' in sys.modules:
    from PyQt5 import QtCore, QtGui, QtWidgets
    QtGui.QApplication = QtWidgets.QApplication
elif 'PySide6.QtCore' in sys.modules:
    from PySide6 import QtCore, QtGui, QtWidgets
    QtGui.QApplication = QtWidgets.QApplication
elif 'PyQt6.QtCore' in sys.modules:
    from PyQt6 import QtCore, QtGui, QtWidgets
    QtGui.QApplication = QtWidgets.QApplication
else:
    try:
        from PyQt5 import QtCore, QtGui, QtWidgets
        QtGui.QApplication = QtWidgets.QApplication
    except ImportError:
        try:
            from PyQt6 import QtCore, QtGui, QtWidgets
            QtGui.QApplication = QtWidgets.QApplication
        except ImportError:
            from PySide6 import QtCore, QtGui, QtWidgets
            QtGui.QApplication = QtWidgets.QApplication
if ("PyQt5.QtCore" in sys.modules) or ("PySide2.QtCore" in sys.modules):
    from ._patch_matplotlib import patch_backend_qt
    patch_backend_qt()
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    TK = False
    QT = True
elif ("PyQt6.QtCore" in sys.modules) or ("PySide6.QtCore" in sys.modules):
    from ._patch_matplotlib import patch_backend_qt
    patch_backend_qt()
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    TK = False
    QT = True
elif ("Tkinter" in sys.modules) or ("tkinter" in sys.modules):
    TK = True
    QT = False
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigureCanvas
else:
    QT = False
    TK = False
    print("Unknown backend. Defaulting to Agg")
    from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
try:
    from .. import PlotBackend
except ImportError:
    from PyMca5.PyMca import PlotBackend
from matplotlib import cm
from matplotlib.font_manager import FontProperties
try:
    from matplotlib.widgets import Cursor
except Exception:
    print("matplotlib.widgets Cursor not available")

from matplotlib.figure import Figure
import matplotlib.patches as patches
Rectangle = patches.Rectangle
Polygon = patches.Polygon
from matplotlib.lines import Line2D
from matplotlib.collections import PathCollection
from matplotlib.text import Text
from matplotlib.image import AxesImage, NonUniformImage
from matplotlib.colors import LinearSegmentedColormap, LogNorm, Normalize
import time

try:
    from . import _utils
except ImportError:
    from PyMca5.PyMcaGraph.backends import _utils

DEBUG = 0

class ModestImage(AxesImage):
    pass

class MatplotlibGraph(FigureCanvas):
    def __init__(self, parent=None, **kw):
        #self.figure = Figure(figsize=size, dpi=dpi) #in inches
        self.fig = Figure()
        if TK:
            self._canvas = FigureCanvas.__init__(self, self.fig, master=parent)
        else:
            self._originalCursorShape = QtCore.Qt.ArrowCursor
            self._canvas = FigureCanvas.__init__(self, self.fig)
            # get the default widget color
            color = self.palette().color(self.backgroundRole())
            color = "#%x" % color.rgb()
            if len(color) == 9:
                color = "#" + color[3:]
            #self.fig.set_facecolor(color)
            self.fig.set_facecolor("w")
            # that's it
        if 1:
            #this almost works
            """
        def twinx(self):
            call signature::

              ax = twinx()

            create a twin of Axes for generating a plot with a sharex
            x-axis but independent y axis.  The y-axis of self will have
            ticks on left and the returned axes will have ticks on the
            right
            ax2 = self.fig.add_axes(self.get_position(True), sharex=self,
                frameon=False)
            ax2.yaxis.tick_right()
            ax2.yaxis.set_label_position('right')
            ax2.yaxis.set_offset_position('right')
            self.ax.yaxis.tick_left()
            ax2.xaxis.set_visible(False)
            return ax2
            """
            self.ax = self.fig.add_axes([.15, .15, .75, .75], label="left")
            self.ax2 = self.ax.twinx()
            self.ax2.set_label("right")

            # critical for picking!!!!
            self.ax2.set_zorder(0)
            self.ax2.set_autoscaley_on(True)
            self.ax.set_zorder(1)
            #this works but the figure color is left
            if hasattr(self.ax, "set_facecolor"):
                self.ax.set_facecolor('none')
            else:
                # this was deprecated in 2.0
                self.ax.set_axis_bgcolor('none')
            self.fig.sca(self.ax)
            try:
                # prevent use of offsets
                self.ax.get_yaxis().get_major_formatter().set_useOffset(False)
                self.ax.get_xaxis().get_major_formatter().set_useOffset(False)
            except Exception:
                print("Error disabling matplotlib offsets")
        else:
            #this almost works
            self.ax2 = self.fig.add_axes([.15, .15, .75, .75],
                                         axisbg="w",
                                         label="right",
                                         frameon=False)
            self.ax = self.fig.add_axes(self.ax2.get_position(),
                                        sharex=self.ax2,
                                        label="left",
                                        frameon=True)
            self.ax2.yaxis.tick_right()
            self.ax2.xaxis.set_visible(False)
            self.ax2.yaxis.set_label_position('right')
            self.ax2.yaxis.set_offset_position('right')
            if hasattr(self.ax, "set_facecolor"):
                self.ax.set_facecolor('none')
            else:
                # this was deprecated in 2.0
                self.ax.set_axis_bgcolor('none')

        # this respects aspect size
        # self.ax = self.fig.add_subplot(111, aspect='equal')
        # This should be independent of Qt
        if QT:
            FigureCanvas.setSizePolicy(self,
                                   QtWidgets.QSizePolicy.Expanding,
                                   QtWidgets.QSizePolicy.Expanding)
            FigureCanvas.updateGeometry(self)
        self.__lastMouseClick = ["middle", time.time()]
        self._zoomEnabled = False
        self._zoomColor = "black"
        self.__zooming = False
        self.__picking = False
        self._background = None
        self.__markerMoving = False
        self._zoomStack = []
        self.xAutoScale = True
        self.yAutoScale = True

        #info text
        self._infoText = None

        #drawingmode handling
        self.setDrawModeEnabled(False)
        self.__drawModeList = ['line', 'hline', 'vline', 'rectangle', 'polygon']
        self.__drawing = False
        self._drawingPatch = None
        self._drawModePatch = 'line'

        #event handling
        self._callback = self._dummyCallback
        self._x0 = None
        self._y0 = None
        self._zoomRectangle = None
        self.fig.canvas.mpl_connect('button_press_event',
                                    self.onMousePressed)
        self.fig.canvas.mpl_connect('button_release_event',
                                    self.onMouseReleased)
        self.fig.canvas.mpl_connect('motion_notify_event',
                                    self.onMouseMoved)
        self.fig.canvas.mpl_connect('scroll_event',
                                    self.onMouseWheel)
        self.fig.canvas.mpl_connect('pick_event',
                                    self.onPick)

    def _dummyCallback(self, ddict):
        if DEBUG:
            print(ddict)

    def setCallback(self, callbackFuntion):
        self._callback = callbackFuntion

    def onPick(self, event):
        # Unfortunately only the artists on the top axes
        # can be picked -> A legend handling widget is
        # needed
        middleButton = 2
        rightButton = 3
        button = event.mouseevent.button
        if button == middleButton:
            # do nothing with the midle button
            return
        elif button == rightButton:
            button = "right"
        else:
            button = "left"
        if self._drawModeEnabled:
            # forget about picking or zooming
            # should one disconnect when setting the mode?
            return
        self.__picking = False
        self._pickingInfo = {}
        if isinstance(event.artist, Line2D) or \
           isinstance(event.artist, PathCollection):
            # we only handle curves and markers for the time being
            self.__picking = True
            artist = event.artist
            label = artist.get_label()
            ind = event.ind
            #xdata = thisline.get_xdata()
            #ydata = thisline.get_ydata()
            #print('onPick line:', zip(numpy.take(xdata, ind),
            #                           numpy.take(ydata, ind)))
            self._pickingInfo['artist'] = artist
            self._pickingInfo['event_ind'] = ind
            if label.startswith("__MARKER__"):
                label = label[10:]
                self._pickingInfo['type'] = 'marker'
                self._pickingInfo['label'] = label
                if 'draggable' in artist._plot_options:
                    self._pickingInfo['draggable'] = True
                else:
                    self._pickingInfo['draggable'] = False
                if 'selectable' in artist._plot_options:
                    self._pickingInfo['selectable'] = True
                else:
                    self._pickingInfo['selectable'] = False
                if hasattr(artist, "_infoText"):
                    self._pickingInfo['infoText'] = artist._infoText
                else:
                    self._pickingInfo['infoText'] = None
            elif isinstance(event.artist, PathCollection):
                # almost identical to line 2D
                self._pickingInfo['type'] = 'curve'
                self._pickingInfo['label'] = label
                self._pickingInfo['artist'] = artist
                data = artist.get_offsets()
                xdata = data[:, 0]
                ydata = data[:, 1]
                self._pickingInfo['xdata'] = xdata[ind]
                self._pickingInfo['ydata'] = ydata[ind]
                self._pickingInfo['infoText'] = None
            else:
                # line2D
                self._pickingInfo['type'] = 'curve'
                self._pickingInfo['label'] = label
                self._pickingInfo['artist'] = artist
                xdata = artist.get_xdata()
                ydata = artist.get_ydata()
                self._pickingInfo['xdata'] = xdata[ind]
                self._pickingInfo['ydata'] = ydata[ind]
                self._pickingInfo['infoText'] = None
            if self._pickingInfo['infoText'] is None:
                if self._infoText is None:
                    self._infoText = self.ax.text(event.mouseevent.xdata,
                                                  event.mouseevent.ydata,
                                                  label)
                else:
                    self._infoText.set_position((event.mouseevent.xdata,
                                                event.mouseevent.ydata))
                    self._infoText.set_text(label)
                self._pickingInfo['infoText'] = self._infoText
            self._pickingInfo['infoText'].set_visible(True)
            if DEBUG:
                print("%s %s selected" % (self._pickingInfo['type'].upper(),
                                          self._pickingInfo['label']))
        elif isinstance(event.artist, Rectangle):
            patch = event.artist
            print('onPick patch:', patch.get_path())
        elif isinstance(event.artist, Text):
            text = event.artist
            print('onPick text:', text.get_text())
        elif isinstance(event.artist, AxesImage):
            self.__picking = True
            artist = event.artist
            #print dir(artist)
            self._pickingInfo['artist'] = artist
            #self._pickingInfo['event_ind'] = ind
            label = artist.get_label()
            self._pickingInfo['type'] = 'image'
            self._pickingInfo['label'] = label
            self._pickingInfo['draggable'] = False
            self._pickingInfo['selectable'] = False
            if hasattr(artist, "_plot_options"):
                if 'draggable' in artist._plot_options:
                    self._pickingInfo['draggable'] = True
                else:
                    self._pickingInfo['draggable'] = False
                if 'selectable' in artist._plot_options:
                    self._pickingInfo['selectable'] = True
                else:
                    self._pickingInfo['selectable'] = False
        else:
            print("unhandled event", event.artist)

    def setDrawModeEnabled(self, flag=True, shape="polygon", label=None,
                           color=None, **kw):
        if flag:
            shape = shape.lower()
            if shape not in self.__drawModeList:
                self._drawModeEnabled = False
                raise ValueError("Unsupported shape %s" % shape)
            else:
                self._drawModeEnabled = True
                self.setZoomModeEnabled(False)
                self._drawModePatch = shape
            self._drawingParameters = kw
            if color is not None:
                self._drawingParameters['color'] = color
            self._drawingParameters['shape'] = shape
            self._drawingParameters['label'] = label
        else:
            self._drawModeEnabled = False

    def setZoomModeEnabled(self, flag=True, color=None):
        if color is None:
            color = self._zoomColor
        if len(color) == 4:
            if type(color[3]) in [type(1), numpy.uint8, numpy.int8]:
                color = numpy.array(color, dtype=numpy.float64)/255.
        self._zoomColor = color
        if flag:
            self._zoomEnabled = True
            self.setDrawModeEnabled(False)
        else:
            self._zoomEnabled = False

    def isZoomModeEnabled(self):
        return self._zoomEnabled

    def isDrawModeEnabled(self):
        return self._drawModeEnabled

    def getDrawMode(self):
        if self.isDrawModeEnabled():
            return self._drawingParameters
        else:
            return None

    def onMousePressed(self, event):
        if DEBUG:
            print("onMousePressed, event = ",event.xdata, event.ydata)
            print("Mouse button = ", event.button)
        self.__time0 = -1.0
        if event.inaxes != self.ax:
            if DEBUG:
                print("RETURNING")
            return
        button = event.button
        leftButton = 1
        middleButton = 2
        rightButton = 3

        self._x0 = event.xdata
        self._y0 = event.ydata

        if button == middleButton:
            # by default, do nothing with the middle button
            return

        self._x0Pixel = event.x
        self._y0Pixel = event.y
        self._x1 = event.xdata
        self._y1 = event.ydata
        self._x1Pixel = event.x
        self._y1Pixel = event.y

        self.__movingMarker = 0
        # picking handling
        if self.__picking:
            if DEBUG:
                print("PICKING, Ignoring zoom")
            self.__zooming = False
            self.__drawing = False
            self.__markerMoving = False
            if self._pickingInfo['type'] == "marker":
                if button == rightButton:
                    # only selection or movement
                    self._pickingInfo = {}
                    return
                artist = self._pickingInfo['artist']
                if button == leftButton:
                    if self._pickingInfo['draggable']:
                        self.__markerMoving = True
                    if self._pickingInfo['selectable']:
                        self.__markerMoving = False
                    if self.__markerMoving:
                        if 'xmarker' in artist._plot_options:
                            data = event.xdata
                            if not numpy.iterable(data):
                                data = [data, ]
                            artist.set_xdata(data)
                        elif 'ymarker' in artist._plot_options:
                            data = event.ydata
                            if not numpy.iterable(data):
                                data = [data, ]
                            artist.set_ydata(data)
                        else:
                            xData, yData = event.xdata, event.ydata
                            if artist._constraint is not None:
                                # Apply marker constraint
                                xData, yData = artist._constraint(xData, yData)
                            if not numpy.iterable(xData):
                                xData = [xData, ]
                            if not numpy.iterable(yData):
                                yData = [yData, ]
                            artist.set_xdata(xData)
                            artist.set_ydata(yData)
                    if BLITTING and hasattr(artist.figure, "canvas"):
                        canvas = artist.figure.canvas
                        axes = artist.axes
                        artist.set_animated(True)
                        canvas.draw()
                        self._background = canvas.copy_from_bbox(self.fig.bbox)
                        axes.draw_artist(artist)
                        canvas.blit(self.fig.bbox)
                    else:
                        self.fig.canvas.draw()
                    ddict = {}
                    ddict['label'] = self._pickingInfo['label']
                    ddict['type'] = self._pickingInfo['type']
                    ddict['draggable'] = self._pickingInfo['draggable']
                    ddict['selectable'] = self._pickingInfo['selectable']
                    ddict['xpixel'] = self._x0Pixel
                    ddict['ypixel'] = self._y0Pixel
                    ddict['xdata'] = artist.get_xdata()
                    ddict['ydata'] = artist.get_ydata()

                    if self.__markerMoving:
                        ddict['event'] = "markerMoving"
                        ddict['x'] = self._x0
                        ddict['y'] = self._y0
                    else:
                        ddict['event'] = "markerClicked"
                        if hasattr(ddict['xdata'], "__len__"):
                            ddict['x'] = ddict['xdata'][-1]
                        else:
                            ddict['x'] = ddict['xdata']
                        if hasattr(ddict['ydata'], "__len__"):
                            ddict['y'] = ddict['ydata'][-1]
                        else:
                            ddict['y'] = ddict['ydata']

                    if button == leftButton:
                        ddict['button'] = "left"
                    else:
                        ddict['button'] = "right"
                    self._callback(ddict)
                    if ddict['event'] == "markerClicked":
                        self.__picking = False
                return
            elif self._pickingInfo['type'] == "curve":
                ddict = {}
                ddict['event'] = "curveClicked"
                #ddict['event'] = "legendClicked"
                ddict['label'] = self._pickingInfo['label']
                ddict['type'] = self._pickingInfo['type']
                ddict['x'] = self._x0
                ddict['y'] = self._y0
                ddict['xpixel'] = self._x0Pixel
                ddict['ypixel'] = self._y0Pixel
                ddict['xdata'] = self._pickingInfo['xdata']
                ddict['ydata'] = self._pickingInfo['ydata']
                if button == leftButton:
                    ddict['button'] = "left"
                else:
                    ddict['button'] = "right"
                self._callback(ddict)
                return
            elif self._pickingInfo['type'] == "image":
                artist = self._pickingInfo['artist']
                ddict = {}
                ddict['event'] = "imageClicked"
                #ddict['event'] = "legendClicked"
                ddict['label'] = self._pickingInfo['label']
                ddict['type'] = self._pickingInfo['type']
                ddict['x'] = self._x0
                ddict['y'] = self._y0
                ddict['xpixel'] = self._x0Pixel
                ddict['ypixel'] = self._y0Pixel
                xScale = artist._plot_info['xScale']
                yScale = artist._plot_info['yScale']
                col = (ddict['x'] - xScale[0])/float(xScale[1])
                row = (ddict['y'] - yScale[0])/float(yScale[1])
                ddict['row'] = int(row)
                ddict['col'] = int(col)
                if button == leftButton:
                    ddict['button'] = "left"
                else:
                    ddict['button'] = "right"
                self.__picking = False
                self._callback(ddict)

        if event.button == rightButton:
            #right click
            self.__zooming = False
            if self._drawingPatch is not None:
                self._emitDrawingSignal("drawingFinished")
            return

        self.__time0 = time.time()
        self.__zooming = self._zoomEnabled
        self._zoomRect = None
        self._xmin, self._xmax  = self.ax.get_xlim()
        self._ymin, self._ymax  = self.ax.get_ylim()
        # deal with inverted axis
        if self._xmin > self._xmax:
            tmpValue = self._xmin
            self._xmin = self._xmax
            self._xmax = tmpValue
        if self._ymin > self._ymax:
            tmpValue = self._ymin
            self._ymin = self._ymax
            self._ymax = tmpValue

        if self.ax.get_aspect() != 'auto':
            self._ratio = (self._ymax - self._ymin) / (self._xmax - self._xmin)

        self.__drawing = self._drawModeEnabled
        if self.__drawing:
            if self._drawModePatch in ['hline', 'vline']:
                if self._drawingPatch is None:
                    self._mouseData = numpy.zeros((2,2), numpy.float32)
                    if self._drawModePatch == "hline":
                        self._mouseData[0,0] = self._xmin
                        self._mouseData[0,1] = self._y0
                        self._mouseData[1,0] = self._xmax
                        self._mouseData[1,1] = self._y0
                    else:
                        self._mouseData[0,0] = self._x0
                        self._mouseData[0,1] = self._ymin
                        self._mouseData[1,0] = self._x0
                        self._mouseData[1,1] = self._ymax
                    color = self._getDrawingColor()
                    self._drawingPatch = Polygon(self._mouseData,
                                             closed=True,
                                             fill=False,
                                             color=color)
                    self.ax.add_patch(self._drawingPatch)

    def _getDrawingColor(self):
        color = "black"
        if "color" in self._drawingParameters:
            color = self._drawingParameters["color"]
            if len(color) == 4:
                if type(color[3]) in [type(1), numpy.uint8, numpy.int8]:
                    color = numpy.array(color, dtype=numpy.float64)/255.
        return color

    def onMouseMoved(self, event):
        if DEBUG:
            print("onMouseMoved, event = ",event.xdata, event.ydata)
        if event.inaxes != self.ax:
            if DEBUG:
                print("RETURNING")
            return

        button = event.button
        if button == 1:
            button = "left"
        elif button == 2:
            button = "middle"
        elif button == 3:
            button = "right"
        else:
            button = None
        #as default, export the mouse in graph coordenates
        self._x1 = event.xdata
        self._y1 = event.ydata
        self._x1Pixel = event.x
        self._y1Pixel = event.y
        ddict= {'event':'mouseMoved',
              'x':self._x1,
              'y':self._y1,
              'xpixel':self._x1Pixel,
              'ypixel':self._y1Pixel,
              'button':button,
              }
        self._callback(ddict)

        if button == "middle":
            return

        # should this be made by Plot1D with the previous call???
        # The problem is Plot1D does not know if one is zooming or drawing
        if not (self.__zooming or self.__drawing or self.__picking):
            # this corresponds to moving without click
            marker = None
            for artist in self.ax.lines:
                label = artist.get_label()
                if label.startswith("__MARKER__"):
                    #data = artist.get_xydata()[0:1]
                    x, y = artist.get_xydata()[-1]
                    pixels = self.ax.transData.transform(numpyvstack([x,y]).T)
                    xPixel, yPixel = pixels.T
                    if 'xmarker' in artist._plot_options:
                        if abs(xPixel-event.x) < 5:
                            marker = artist
                    elif 'ymarker' in artist._plot_options:
                        if abs(yPixel-event.y) < 5:
                            marker = artist
                    elif (abs(xPixel-event.x) < 5) and \
                         (abs(yPixel-event.y) < 5):
                        marker = artist
                if marker is not None:
                    break
            if QT:
                oldShape = self.cursor().shape()
                if oldShape not in [QtCore.Qt.SizeHorCursor,
                                QtCore.Qt.SizeVerCursor,
                                QtCore.Qt.PointingHandCursor,
                                QtCore.Qt.OpenHandCursor,
                                QtCore.Qt.SizeAllCursor]:
                    self._originalCursorShape = oldShape
            if marker is not None:
                ddict = {}
                ddict['event'] = 'hover'
                ddict['type'] = 'marker'
                ddict['label'] = marker.get_label()[10:]
                if 'draggable' in marker._plot_options:
                    ddict['draggable'] = True
                    if QT:
                        if 'ymarker' in artist._plot_options:
                            self.setCursor(QtGui.QCursor(QtCore.Qt.SizeVerCursor))
                        elif 'xmarker' in artist._plot_options:
                            self.setCursor(QtGui.QCursor(QtCore.Qt.SizeHorCursor))
                        else:
                            self.setCursor(QtGui.QCursor(QtCore.Qt.SizeAllCursor))
                else:
                    ddict['draggable'] = False
                if 'selectable' in marker._plot_options:
                    ddict['selectable'] = True
                    if QT:
                        self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
                else:
                    ddict['selectable'] = False
                ddict['x'] = self._x1
                ddict['y'] = self._y1
                ddict['xpixel'] = self._x1Pixel
                ddict['ypixel'] = self._y1Pixel
                self._callback(ddict)
            elif QT:
                if self._originalCursorShape in [QtCore.Qt.SizeHorCursor,
                                QtCore.Qt.SizeVerCursor,
                                QtCore.Qt.PointingHandCursor,
                                QtCore.Qt.OpenHandCursor,
                                QtCore.Qt.SizeAllCursor]:
                    self.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
                else:
                    self.setCursor(QtGui.QCursor(self._originalCursorShape))
            return
        if self.__picking:
            if self.__markerMoving:
                artist = self._pickingInfo['artist']
                infoText = self._pickingInfo['infoText']
                if 'xmarker' in artist._plot_options:
                    data = event.xdata
                    if not numpy.iterable(data):
                        data = [data, ]
                    artist.set_xdata(data)
                    ymin, ymax = self.ax.get_ylim()
                    delta = abs(ymax - ymin)
                    ymax = max(ymax, ymin) - 0.005 * delta
                    if infoText is not None:
                        infoText.set_position((event.xdata, ymax))
                elif 'ymarker' in artist._plot_options:
                    data = event.ydata
                    if not numpy.iterable(data):
                        data = [data, ]
                    artist.set_ydata(data)
                    if infoText is not None:
                        infoText.set_position((event.xdata, event.ydata))
                else:
                    xData, yData = event.xdata, event.ydata
                    if artist._constraint is not None:
                        # Apply marker constraint
                        xData, yData = artist._constraint(xData, yData)
                    if not numpy.iterable(xData):
                        xData = [xData, ]
                    if not numpy.iterable(yData):
                        yData = [yData, ]
                    artist.set_xdata(xData)
                    artist.set_ydata(yData)
                    if infoText is not None:
                        xtmp, ytmp = self.ax.transData.transform_point((xData,
                                                                        yData))
                        inv = self.ax.transData.inverted()
                        xtmp, ytmp = inv.transform_point((xtmp, ytmp + 15))
                        infoText.set_position((xData, ytmp))
                if BLITTING and (self._background is not None) and\
                       hasattr(artist.figure, "canvas"):
                    canvas = artist.figure.canvas
                    axes = artist.axes
                    artist.set_animated(True)
                    canvas.restore_region(self._background)
                    axes.draw_artist(artist)
                    canvas.blit(self.fig.bbox)
                else:
                    self.fig.canvas.draw()
                ddict = {}
                ddict['event'] = "markerMoving"
                ddict['button'] = "left"
                ddict['label'] = self._pickingInfo['label']
                ddict['type'] = self._pickingInfo['type']
                ddict['draggable'] = self._pickingInfo['draggable']
                ddict['selectable'] = self._pickingInfo['selectable']
                ddict['x'] = self._x1
                ddict['y'] = self._y1
                ddict['xpixel'] = self._x1Pixel
                ddict['ypixel'] = self._y1Pixel
                ddict['xdata'] = artist.get_xdata()
                ddict['ydata'] = artist.get_ydata()
                self._callback(ddict)
            return
        if (not self.__zooming) and (not self.__drawing):
            return

        if self._x0 is None:
            # this happened when using the middle button
            return

        if self.__zooming or \
           (self.__drawing and (self._drawModePatch == 'rectangle')):
            if self._x1 < self._xmin:
                self._x1 = self._xmin
            elif self._x1 > self._xmax:
                self._x1 = self._xmax
            if self._y1 < self._ymin:
                self._y1 = self._ymin
            elif self._y1 > self._ymax:
                self._y1 = self._ymax

            if self._x1 < self._x0:
                x = self._x1
                w = self._x0 - self._x1
            else:
                x = self._x0
                w = self._x1 - self._x0
            if self._y1 < self._y0:
                y = self._y1
                h = self._y0 - self._y1
            else:
                y = self._y0
                h = self._y1 - self._y0
            if w == 0:
                return
            if (not self.__drawing) and (self.ax.get_aspect() != 'auto'):
                if (h / w) > self._ratio:
                    h = w * self._ratio
                else:
                    w = h / self._ratio
                if self._x1 > self._x0:
                    x = self._x0
                else:
                    x = self._x0 - w
                if self._y1 > self._y0:
                    y = self._y0
                else:
                    y = self._y0 - h

            if self.__zooming:
                if self._zoomRectangle is None:
                    self._zoomRectangle = Rectangle(xy=(x,y),
                                                   width=w,
                                                   height=h,
                                                   color=self._zoomColor,
                                                   fill=False)
                    self.ax.add_patch(self._zoomRectangle)
                else:
                    self._zoomRectangle.set_bounds(x, y, w, h)
                    #self._zoomRectangle._update_patch_transform()
                if BLITTING:
                    artist = self._zoomRectangle
                    canvas = artist.figure.canvas
                    axes = artist.axes
                    artist.set_animated(True)
                    if self._background is None:
                        self._background = canvas.copy_from_bbox(self.fig.bbox)
                    canvas.restore_region(self._background)
                    axes.draw_artist(artist)
                    canvas.blit(self.fig.bbox)
                else:
                    self.fig.canvas.draw()
                return
            else:
                if self._drawingPatch is None:
                    color = self._getDrawingColor()
                    self._drawingPatch = Rectangle(xy=(x,y),
                                                   width=w,
                                                   height=h,
                                                   fill=False,
                                                   color=color)
                    self._drawingPatch.set_hatch('.')
                    self.ax.add_patch(self._drawingPatch)
                else:
                    self._drawingPatch.set_bounds(x, y, w, h)
                    #self._zoomRectangle._update_patch_transform()
        if self.__drawing:
            if self._drawingPatch is None:
                self._mouseData = numpy.zeros((2,2), numpy.float32)
                self._mouseData[0,0] = self._x0
                self._mouseData[0,1] = self._y0
                self._mouseData[1,0] = self._x1
                self._mouseData[1,1] = self._y1
                color = self._getDrawingColor()
                self._drawingPatch = Polygon(self._mouseData,
                                             closed=True,
                                             fill=False,
                                             color=color)
                self.ax.add_patch(self._drawingPatch)
            elif self._drawModePatch == 'rectangle':
                # already handled, just for compatibility
                self._mouseData = numpy.zeros((2,2), numpy.float32)
                self._mouseData[0,0] = self._x0
                self._mouseData[0,1] = self._y0
                self._mouseData[1,0] = self._x1
                self._mouseData[1,1] = self._y1
            elif self._drawModePatch == 'line':
                self._mouseData[0,0] = self._x0
                self._mouseData[0,1] = self._y0
                self._mouseData[1,0] = self._x1
                self._mouseData[1,1] = self._y1
                self._drawingPatch.set_xy(self._mouseData)
            elif self._drawModePatch == 'hline':
                xmin, xmax = self.ax.get_xlim()
                self._mouseData[0,0] = xmin
                self._mouseData[0,1] = self._y1
                self._mouseData[1,0] = xmax
                self._mouseData[1,1] = self._y1
                self._drawingPatch.set_xy(self._mouseData)
            elif self._drawModePatch == 'vline':
                ymin, ymax = self.ax.get_ylim()
                self._mouseData[0,0] = self._x1
                self._mouseData[0,1] = ymin
                self._mouseData[1,0] = self._x1
                self._mouseData[1,1] = ymax
                self._drawingPatch.set_xy(self._mouseData)
            elif self._drawModePatch == 'polygon':
                self._mouseData[-1,0] = self._x1
                self._mouseData[-1,1] = self._y1
                self._drawingPatch.set_xy(self._mouseData)
                if matplotlib.__version__.startswith('1.1.1'):
                    # Patch for Debian 7
                    # Workaround matplotlib issue with closed path
                    # Need to toggle closed path to rebuild points
                    self._drawingPatch.set_closed(False)
                self._drawingPatch.set_closed(True)
                self._drawingPatch.set_hatch('/')
            if BLITTING:
                if self._background is None:
                    artist = self._drawingPatch
                    canvas = artist.figure.canvas
                    axes = artist.axes
                    self._background = canvas.copy_from_bbox(self.fig.bbox)
                artist = self._drawingPatch
                canvas = artist.figure.canvas
                axes = artist.axes
                artist.set_animated(True)
                canvas.restore_region(self._background)
                axes.draw_artist(artist)
                canvas.blit(self.fig.bbox)
            else:
                self.fig.canvas.draw()
            self._emitDrawingSignal(event='drawingProgress')


    def onMouseReleased(self, event):
        if DEBUG:
            print("onMouseReleased, event = ",event.xdata, event.ydata)
        if self._infoText in self.ax.texts:
            self._infoText.set_visible(False)
        if self.__picking:
            self.__picking = False
            if self.__markerMoving:
                self.__markerMoving = False
                artist = self._pickingInfo['artist']
                if BLITTING and hasattr(artist.figure, "canvas"):
                    artist.set_animated(False)
                    self._background = None
                    artist.figure.canvas.draw()
                ddict = {}
                ddict['event'] = "markerMoved"
                ddict['label'] = self._pickingInfo['label']
                ddict['type'] = self._pickingInfo['type']
                ddict['draggable'] = self._pickingInfo['draggable']
                ddict['selectable'] = self._pickingInfo['selectable']
                # use this and not the current mouse position because
                # it has to agree with the marker position
                ddict['x'] = artist.get_xdata()
                ddict['y'] = artist.get_ydata()
                ddict['xdata'] = artist.get_xdata()
                ddict['ydata'] = artist.get_ydata()
                # matplotlib 3.7.x was giving different output than previous versions
                for key in ["x", "y", "xdata", "ydata"]:
                    if hasattr(ddict[key], "__len__"):
                        if len(ddict[key]) == 1:
                            ddict[key] = ddict[key][0]
                self._callback(ddict)
            self._pickingInfo = {}
            return

        if not hasattr(self, "__zoomstack"):
            self.__zoomstack = []

        if event.button == 3:
            #right click
            if self.__drawing:
                self.__drawing = False
                #self._drawingPatch = None
                ddict = {}
                ddict['event'] = 'drawingFinished'
                ddict['type']  = '%s' % self._drawModePatch
                ddict['data']  = self._mouseData * 1
                self._emitDrawingSignal(event='drawingFinished')
                return

            self.__zooming = False
            if len(self._zoomStack):
                xmin, xmax, ymin, ymax, y2min, y2max = self._zoomStack.pop()
                self.setLimits(xmin, xmax, ymin, ymax, y2min, y2max)
                self.draw()

        if self.__drawing and (self._drawingPatch is not None):
            nrows, ncols = self._mouseData.shape
            if self._drawModePatch in ['polygon']:
                self._mouseData = numpy.resize(self._mouseData, (nrows+1,2))
            self._mouseData[-1,0] = self._x1
            self._mouseData[-1,1] = self._y1
            self._drawingPatch.set_xy(self._mouseData)
            if self._drawModePatch not in ['polygon']:
                self._emitDrawingSignal("drawingFinished")

        if self._x0 is None:
            if event.inaxes != self.ax:
                if DEBUG:
                    print("on MouseReleased RETURNING")
            else:
                print("How can it be here???")
            return
        if self._zoomRectangle is None:
            currentTime = time.time()
            deltaT =  currentTime - self.__time0
            if (deltaT < 0.150) or (self.__time0 < 0) or (not self.__zooming) or\
               ((self._x1 == self._x0) and (self._y1 == self._y0)):
                # single or double click, no zooming
                self.__zooming = False
                ddict = {'x':event.xdata,
                         'y':event.ydata,
                         'xpixel':event.x,
                         'ypixel':event.y}
                leftButton = 1
                middleButton = 2
                rightButton = 3
                button = event.button
                if button == rightButton:
                    ddict['button'] = "right"
                elif button == middleButton:
                    ddict['button'] = "middle"
                else:
                    ddict['button'] = "left"
                if (button == self.__lastMouseClick[0]) and\
                   ((currentTime - self.__lastMouseClick[1]) < 0.6):
                    ddict['event'] = "mouseDoubleClicked"
                else:
                    ddict['event'] = "mouseClicked"
                self.__lastMouseClick = [button, time.time()]
                self._callback(ddict)
                return

        if self._zoomRectangle is not None:
            x, y = self._zoomRectangle.get_xy()
            w = self._zoomRectangle.get_width()
            h = self._zoomRectangle.get_height()
            self._zoomRectangle.remove()
            self._x0 = None
            self._y0 = None
            if BLITTING:
                artist = self._zoomRectangle
                axes = artist.axes
                artist.set_animated(False)
                self._background = None
            self._zoomRectangle = None
            if (w != 0) and (h != 0):
                # don't do anything
                xmin, xmax = self.ax.get_xlim()
                ymin, ymax = self.ax.get_ylim()
                if ymax < ymin:
                    ymin, ymax = ymax, ymin

                if not self.ax2.get_yaxis().get_visible():
                    y2min, y2max = None, None
                    newY2Min, newY2Max = None, None
                else:
                    bottom, top = self.ax2.get_ylim()
                    y2min, y2max = min(bottom, top), max(bottom, top)

                    # Convert corners from ax data to window
                    pt0 = self.ax.transData.transform_point((x, y))
                    pt1 = self.ax.transData.transform_point((x + w, y + h))
                    # Convert corners from window to ax2 data
                    pt0 = self.ax2.transData.inverted().transform_point(pt0)
                    pt1 = self.ax2.transData.inverted().transform_point(pt1)

                    # Get min and max on right Y axis
                    newY2Min, newY2Max = pt0[1], pt1[1]
                    if newY2Max < newY2Min:
                        newY2Min, newY2Max = newY2Max, newY2Min

                self._zoomStack.append((xmin, xmax, ymin, ymax, y2min, y2max))
                self.setLimits(x, x+w, y, y+h, newY2Min, newY2Max)
            self.draw()

    @staticmethod
    def _newZoomRange(min_, max_, center, scale, isLog):
        if isLog:
            if min_ > 0.:
                oldMin = numpy.log10(min_)
            else:
                # Happens when autoscale is off and switch to log scale
                # while displaying area < 0.
                oldMin = numpy.log10(numpy.nextafter(0, 1))

            if center > 0.:
                center = numpy.log10(center)
            else:
                center = numpy.log10(numpy.nextafter(0, 1))

            if max_ > 0.:
                oldMax = numpy.log10(max_)
            else:
                # Should not happen
                oldMax = 0.
        else:
            oldMin, oldMax = min_, max_

        offset = (center - oldMin) / (oldMax - oldMin)
        range_ = (oldMax - oldMin) / scale
        newMin = center - offset * range_
        newMax = center + (1. - offset) * range_
        if isLog:
            try:
                newMin, newMax = 10. ** float(newMin), 10. ** float(newMax)
            except OverflowError:  # Limit case
                newMin, newMax = min_, max_
            if newMin <= 0. or newMax <= 0.:  # Limit case
                newMin, newMax = min_, max_
        return newMin, newMax

    def onMouseWheel(self, event):
        if not self.isZoomModeEnabled():
            return

        if event.xdata is None or event.ydata is None:
            return

        scaleF = 1.1 if event.step > 0 else 1 / 1.1

        xLim = self.ax.get_xlim()
        xMin, xMax = min(xLim), max(xLim)
        isXLog = (self.ax.get_xscale() == 'log')

        yLim = self.ax.get_ylim()
        yMin, yMax = min(yLim), max(yLim)
        isYLog = (self.ax.get_yscale() == 'log')

        # If negative limit and log scale,
        # try to get a positive limit from the data limits
        if (isXLog and xMin <= 0.) or (isYLog and yMin <= 0.):
            bounds = self.getDataLimits()
            if isXLog:
                if xMin <= 0. and bounds[0] > 0.:
                    xMin = bounds[0]
                if xMax <= 0. and bounds[1] > 0.:
                    xMax = bounds[1]

            if isYLog:
                if yMin <= 0. and bounds[2] > 0.:
                    yMin = bounds[2]
                if yMax <= 0. and bounds[3] > 0.:
                    yMax = bounds[3]

        xMin, xMax = self._newZoomRange(xMin, xMax,
                                        event.xdata, scaleF, isXLog)

        yMin, yMax = self._newZoomRange(yMin, yMax,
                                        event.ydata, scaleF, isYLog)

        if self.ax2.get_yaxis().get_visible():
            # Get y position in right axis coords
            x, y2Data = self.ax2.transData.inverted().transform_point(
                (event.x, event.y))

            y2Lim = self.ax2.get_ylim()
            y2Min, y2Max = min(y2Lim), max(y2Lim)
            isY2Log = (self.ax2.get_yscale() == 'log')

            # If negative limit and log scale,
            # try to get a positive limit from the data limits
            if isY2Log and y2Min <= 0.:
                bounds = self.getDataLimits('right')
                if yMin <= 0. and bounds[2] > 0.:
                    y2Min = bounds[2]
                if yMax <= 0. and bounds[3] > 0.:
                    y2Max = bounds[3]

            y2Min, y2Max = self._newZoomRange(y2Min, y2Max,
                                              y2Data, scaleF, isYLog)
            self.setLimits(xMin, xMax, yMin, yMax, y2Min, y2Max)
        else:
            self.setLimits(xMin, xMax, yMin, yMax)

        self.draw()

    def _emitDrawingSignal(self, event="drawingFinished"):
        ddict = {}
        ddict['event'] = event
        ddict['type'] = '%s' % self._drawModePatch
        #ddict['xdata'] = numpy.array(self._drawingPatch.get_x())
        #ddict['ydata'] = numpy.array(self._drawingPatch.get_y())
        #print(dir(self._drawingPatch))
        a = self._drawingPatch.get_xy()
        ddict['points'] = numpy.array(a)
        ddict['points'].shape = -1, 2
        ddict['xdata'] = ddict['points'][:, 0]
        ddict['ydata'] = ddict['points'][:, 1]
        #print(numpyvstack(a))
        #pixels = self.ax.transData.transform(numpyvstack(a).T)
        #xPixel, yPixels = pixels.T
        if self._drawModePatch in ["rectangle", "circle"]:
            # we need the rectangle containing it
            ddict['x'] = ddict['points'][:, 0].min()
            ddict['y'] = ddict['points'][:, 1].min()
            ddict['width'] = self._drawingPatch.get_width()
            ddict['height'] = self._drawingPatch.get_height()
        elif self._drawModePatch in ["ellipse"]:
            #we need the rectangle but given the four corners
            pass
        ddict['parameters'] = {}
        for key in self._drawingParameters.keys():
            ddict['parameters'][key] = self._drawingParameters[key]
        if event == "drawingFinished":
            self.__drawingParameters = None
            self.__drawing = False
            if self._drawingPatch is not None:
                if BLITTING:
                    artist = self._drawingPatch
                    artist.set_animated(False)
                    self._background = None
            self._drawingPatch.remove()
            self._drawingPatch = None
            self.draw()
        self._callback(ddict)

    def emitLimitsChangedSignal(self):
        # Send event about limits changed
        left, right = self.ax.get_xlim()
        xRange = (left, right) if left < right else (right, left)

        bottom, top = self.ax.get_ylim()
        yRange = (bottom, top) if bottom < top else (top, bottom)

        if hasattr(self.ax2, "get_visible") and self.ax2.get_visible():
            bottom2, top2 = self.ax2.get_ylim()
            y2Range = (bottom2, top2) if bottom2 < top2 else (top2, bottom2)
        else:
            y2Range = None

        if hasattr(self, "get_tk_widget"):
            sourceObj = self.get_tk_widget()
        else:
            sourceObj = self

        eventDict = {
            'event': 'limitsChanged',
            'source': id(sourceObj),
            'xdata': xRange,
            'ydata': yRange,
            'y2data': y2Range,
        }
        self._callback(eventDict)

    def setLimits(self, xmin, xmax, ymin, ymax, y2min=None, y2max=None):
        delta = 0.044
        if xmin == xmax:
            xmax += delta
            xmin -= delta
        self.ax.set_xlim(xmin, xmax)
        if ymin == ymax:
            ymax += delta
            ymin -= delta
        if ymax < ymin:
            ymin, ymax = ymax, ymin
        current = self.ax.get_ylim()
        if self.ax.yaxis_inverted():
            self.ax.set_ylim(ymax, ymin)
            #top, bottom = current
        else:
            self.ax.set_ylim(ymin, ymax)
            #bottom, top = current

        if y2min is not None and y2max is not None:
            if y2max < y2min:
                y2min, y2max = y2max, y2min
            if self.ax2.yaxis_inverted():
                bottom, top = y2max, y2min
            else:
                bottom, top = y2min, y2max
            self.ax2.set_ylim(bottom, top)

        # if second axis was not properly initialized, this does not work
        #if 0 and hasattr(self.ax2, "get_visible") and self.ax2.get_visible():
        #    #print("BOTTOM, TOP = ", bottom, top)
        #    bottom2, top2 = self.ax2.get_ylim()
        #    #print("BOTTOM2, TO2 = ", bottom2, top2)
        #    i2Range = top2 - bottom2
        #    if i2Range > 0:
        #        ymin2 = bottom2 + i2Range * (ymin - bottom)/(top - bottom)
        #        ymax2 = bottom2 + i2Range * (ymax - bottom)/(top - bottom)
        #        #print("OBTAINED = ", ymin2, ymax2)
        #        if self.ax2.yaxis_inverted():
        #            self.ax2.set_ylim(ymax2, ymin2)
        #        else:
        #            self.ax2.set_ylim(ymin2, ymax2)
        # Next line forces a square display region
        #self.ax.set_aspect((xmax-xmin)/float(ymax-ymin))
        #self.draw()
        self.emitLimitsChangedSignal()

    def resetZoom(self, dataMargins=None):
        xmin, xmax, ymin, ymax = self.getDataLimits('left')
        if hasattr(self.ax2, "get_visible"):
            if self.ax2.get_visible():
                xmin2, xmax2, ymin2, ymax2 = self.getDataLimits('right')
            else:
                xmin2 = None
                xmax2 = None
        else:
            xmin2, xmax2, ymin2, ymax2 = self.getDataLimits('right')
        #self.ax2.set_ylim(ymin2, ymax2)
        if (xmin2 is not None) and ((xmin2 != 0) or (xmax2 != 1)):
            xmin = min(xmin, xmin2)
            xmax = max(xmax, xmax2)

        # Add margins around data inside the plot area
        if xmin2 is None:
            newLimits = _utils.addMarginsToLimits(
                dataMargins,
                self.ax.get_xscale() == 'log', self.ax.get_yscale() == 'log',
                xmin, xmax, ymin, ymax)

            self.setLimits(*newLimits)
        else:
            newLimits = _utils.addMarginsToLimits(
                dataMargins,
                self.ax.get_xscale() == 'log', self.ax.get_yscale() == 'log',
                xmin, xmax, ymin, ymax, ymin2, ymax2)

            self.setLimits(*newLimits)

        #self.ax2.set_autoscaley_on(True)
        self._zoomStack = []

    def getDataLimits(self, axesLabel='left'):
        if axesLabel == 'right':
            axes = self.ax2
        else:
            axes = self.ax
        if DEBUG:
            print("CALCULATING limits ", axes.get_label())
        xmin = None
        for line2d in axes.lines:
            label = line2d.get_label()
            if label.startswith("__MARKER__"):
                #it is a marker
                continue
            lineXMin = None
            if hasattr(line2d, "_plot_info"):
                if line2d._plot_info.get("axes", "left") != axesLabel:
                    continue
                if "xmin" in line2d._plot_info:
                    lineXMin = line2d._plot_info["xmin"]
                    lineXMax = line2d._plot_info["xmax"]
                    lineYMin = line2d._plot_info["ymin"]
                    lineYMax = line2d._plot_info["ymax"]
            if lineXMin is None:
                x = line2d.get_xdata()
                y = line2d.get_ydata()
                if not len(x) or not len(y):
                    continue
                lineXMin = nanmin(x)
                lineXMax = nanmax(x)
                lineYMin = nanmin(y)
                lineYMax = nanmax(y)
            if xmin is None:
                xmin = lineXMin
                xmax = lineXMax
                ymin = lineYMin
                ymax = lineYMax
                continue
            xmin = min(xmin, lineXMin)
            xmax = max(xmax, lineXMax)
            ymin = min(ymin, lineYMin)
            ymax = max(ymax, lineYMax)

        for line2d in axes.collections:
            label = line2d.get_label()
            if label.startswith("__MARKER__"):
                #it is a marker
                continue
            lineXMin = None
            if hasattr(line2d, "_plot_info"):
                if line2d._plot_info.get("axes", "left") != axesLabel:
                    continue
                if "xmin" in line2d._plot_info:
                    lineXMin = line2d._plot_info["xmin"]
                    lineXMax = line2d._plot_info["xmax"]
                    lineYMin = line2d._plot_info["ymin"]
                    lineYMax = line2d._plot_info["ymax"]
            if lineXMin is None:
                print("CANNOT CALCULATE LIMITS")
                continue
            if xmin is None:
                xmin = lineXMin
                xmax = lineXMax
                ymin = lineYMin
                ymax = lineYMax
                continue
            xmin = min(xmin, lineXMin)
            xmax = max(xmax, lineXMax)
            ymin = min(ymin, lineYMin)
            ymax = max(ymax, lineYMax)

        for artist in axes.images:
            x0, x1, y0, y1 = artist.get_extent()
            if (xmin is None):
                xmin = x0
                xmax = x1
                ymin = min(y0, y1)
                ymax = max(y0, y1)
            xmin = min(xmin, x0)
            xmax = max(xmax, x1)
            ymin = min(ymin, y0)
            ymax = max(ymax, y1)

        for artist in axes.artists:
            label = artist.get_label()
            if label.startswith("__IMAGE__"):
                if hasattr(artist, 'get_image_extent'):
                    x0, x1, y0, y1 = artist.get_image_extent()
                else:
                    x0, x1, y0, y1 = artist.get_extent()
                if (xmin is None):
                    xmin = x0
                    xmax = x1
                    ymin = min(y0, y1)
                    ymax = max(y0, y1)
                ymin = min(ymin, y0, y1)
                ymax = max(ymax, y1, y0)
                xmin = min(xmin, x0)
                xmax = max(xmax, x1)

        if xmin is None:
            xmin = 0
            xmax = 1
            ymin = 0
            ymax = 1
            if axesLabel == 'right':
                return None, None, None, None

        xSize = float(xmax - xmin)
        ySize = float(ymax - ymin)
        A = self.ax.get_aspect()
        if A != 'auto':
            figW, figH = self.ax.get_figure().get_size_inches()
            figAspect = figH / figW

            #l, b, w, h = self.ax.get_position(original=True).bounds
            #box_aspect = figAspect * (h / float(w))

            #dataRatio = box_aspect / A
            dataRatio = (ySize / xSize) * A

            y_expander = dataRatio - figAspect
            # If y_expander > 0, the dy/dx viewLim ratio needs to increase
            if abs(y_expander) < 0.005:
                #good enough
                pass
            else:
                # this works for any data ratio
                if y_expander < 0:
                    #print("adjust_y")
                    deltaY = xSize * (figAspect / A) - ySize
                    yc = 0.5 * (ymin + ymax)
                    ymin = yc - (ySize + deltaY) * 0.5
                    ymax = yc + (ySize + deltaY) * 0.5
                else:
                    #print("ADJUST X")
                    deltaX = ySize * (A / figAspect) - xSize
                    xc = 0.5 * (xmin + xmax)
                    xmin = xc - (xSize + deltaX) * 0.5
                    xmax = xc + (xSize + deltaX) * 0.5
        if DEBUG:
            print("CALCULATED LIMITS = ", xmin, xmax, ymin, ymax)
        return xmin, xmax, ymin, ymax

    def resizeEvent(self, ev):
        # we have to get rid of the copy of the underlying image
        self._background = None
        FigureCanvas.resizeEvent(self, ev)

    if DEBUG:
        def draw(self):
            print("Draw called")
            super(MatplotlibGraph, self).draw()

class MatplotlibBackend(PlotBackend.PlotBackend):
    def __init__(self, parent=None, **kw):
       	#self.figure = Figure(figsize=size, dpi=dpi) #in inches
        self.graph = MatplotlibGraph(parent, **kw)
        self.ax2 = self.graph.ax2
        self.ax = self.graph.ax
        PlotBackend.PlotBackend.__init__(self, parent)
        self._parent = parent
        self._logX = False
        self._logY = False
        self.setZoomModeEnabled = self.graph.setZoomModeEnabled
        self.setDrawModeEnabled = self.graph.setDrawModeEnabled
        self.isZoomModeEnabled = self.graph.isZoomModeEnabled
        self.isDrawModeEnabled = self.graph.isDrawModeEnabled
        self.getDrawMode = self.graph.getDrawMode
        self._oldActiveCurve = None
        self._oldActiveCurveLegend = None
        # should one have two methods, for enable and for show
        self._rightAxisEnabled = False
        self.enableAxis('right', False)
        self._graphCursor = None
        self.matplotlibVersion = matplotlib.__version__

    def setGraphCursor(self, flag=True, color=None, linewidth=None, linestyle=None):
        if color is None:
            color = "black"
        if linewidth is None:
            linewidth = 1
        if linestyle is None:
            linestyle="-"
        self._graphCursorConfiguration = (color, linewidth, linestyle)
        if flag:
            if self._graphCursor is None:
                self._graphCursor = Cursor(self.ax,
                                       useblit=True,
                                       color=color,
                                       linewidth=linewidth,
                                       linestyle=linestyle)
                self.ax.get_figure().canvas.draw()
            self._graphCursor.visible = True
        else:
            if self._graphCursor is not None:
                self._graphCursor.visible = False

    def getGraphCursor(self):
        if self._graphCursor is None:
            return None
        elif not self._graphCursor.visible:
            return None
        else:
            return self._graphCursorConfiguration * 1            

    def addCurve(self, x, y, legend=None, info=None, replace=False, replot=True,
                 color=None, symbol=None, linewidth=None, linestyle=None,
                 xlabel=None, ylabel=None, yaxis=None,
                 xerror=None, yerror=None, z=1, selectable=True, **kw):
        if legend is None:
            legend = "Unnamed curve"
        if replace:
            self.clearCurves()
        else:
            self.removeCurve(legend, replot=False)
        if color is None:
            color = self._activeCurveColor
        if len(color) == 4:
            if type(color[3]) in [type(1), numpy.uint8, numpy.int8]:
                color = numpy.array(color, dtype=numpy.float64)/255.

        brush = color
        style = linestyle
        if linewidth is None:
            linewidth = 1
        if yaxis == "right":
            axisId = yaxis
        else:
            axisId = "left"
        fill = kw.get('plot_fill', False)
        if axisId == "right":
            axes = self.ax2
            if self._rightAxisEnabled is None:
                # never initialized
                self.enableAxis(axisId, True)
        else:
            axes = self.ax
        pickradius = 3
        if selectable:
            picker = True
        else:
            picker = False
        scatterPlot = False
        if hasattr(color, "dtype"):
            if len(color) == len(x):
                scatterPlot = True
        if scatterPlot:
            # scatter plot
            if color.dtype not in [numpy.float32, numpy.float64]:
                actualColor = color / 255.
            else:
                actualColor = color
            pathObject = axes.scatter(x, y,
                                      label=legend,
                                      color=actualColor,
                                      marker=symbol,
                                      picker=picker,
                                      pickradius=pickradius)

            if style not in [" ", None]:
                # scatter plot with an actual line ...
                # we need to assign a color ...
                curveList = axes.plot(x, y, label=legend,
                                      linestyle=style,
                                      color=actualColor[0],
                                      linewidth=linewidth,
                                      picker=picker,
                                      marker=None,
                                      pickradius=pickradius,
                                      **kw)
                curveList[-1]._plot_info = {'color':actualColor,
                                              'linewidth':linewidth,
                                              'brush':brush,
                                              'style':style,
                                              'symbol':symbol,
                                              'label':legend,
                                              'axes':axisId,
                                              'fill':fill,
                                              'xlabel':xlabel,
                                              'ylabel':ylabel}
                if hasattr(x, "min") and hasattr(y, "min"):
                    curveList[-1]._plot_info['xmin'] = nanmin(x)
                    curveList[-1]._plot_info['xmax'] = nanmax(x)
                    curveList[-1]._plot_info['ymin'] = nanmin(y)
                    curveList[-1]._plot_info['ymax'] = nanmax(y)
            # scatter plot is a collection
            curveList = [pathObject]
            if self._logY:
                axes.set_yscale('log')
        elif self._logY:
            curveList = axes.semilogy( x, y, label=legend,
                                          linestyle=style,
                                          color=color,
                                          linewidth=linewidth,
                                          picker=picker,
                                          pickradius=pickradius,
                                          **kw)
        else:
            curveList = axes.plot( x, y, label=legend,
                                  linestyle=style,
                                  color=color,
                                  linewidth=linewidth,
                                  picker=picker,
                                  pickradius=pickradius,
                                  **kw)

        # errorbar is a container?
        #axes.errorbar(x,y, label=legend,yerr=numpy.sqrt(y), linestyle=" ",color='b')

        # nice effects:
        #curveList[-1].set_drawstyle('steps-mid')
        if fill:
            axes.fill_between(x, 1.0e-8, y)
        #curveList[-1].set_fillstyle('bottom')
        if hasattr(curveList[-1], "set_marker"):
            if symbol is None:
                symbol = "None"
            curveList[-1].set_marker(symbol)
        curveList[-1]._plot_info = {'color':color,
                                      'linewidth':linewidth,
                                      'brush':brush,
                                      'style':style,
                                      'symbol':symbol,
                                      'label':legend,
                                      'axes':axisId,
                                      'fill':fill,
                                      'xlabel':xlabel,
                                      'ylabel':ylabel}
        if hasattr(x, "min") and hasattr(y, "min"):
            # this is needed for scatter plots because I do not know
            # how to recover the data yet, it can speed up limits too
            curveList[-1]._plot_info['xmin'] = nanmin(x)
            curveList[-1]._plot_info['xmax'] = nanmax(x)
            curveList[-1]._plot_info['ymin'] = nanmin(y)
            curveList[-1]._plot_info['ymax'] = nanmax(y)
        if self._activeCurveHandling:
            if self._oldActiveCurve in self.ax.lines:
                if self._oldActiveCurve.get_label() == legend:
                    curveList[-1].set_color(self._activeCurveColor)
            elif self._oldActiveCurveLegend == legend:
                curveList[-1].set_color(self._activeCurveColor)
        curveList[-1].axes = axes # set_axes(axes) deprecated in version 1.5
        curveList[-1].set_zorder(z)
        if replot:
            self.replot()
        # If I return the instance, later on cannot make a copy.deepcopy
        # of the info and asks me to use "frozen" instead
        #return curveList[-1]
        return legend

    def addItem(self, x, y, legend, info=None, replace=False, replot=True, **kw):
        if replace:
            self.clearItems()
        else:
            # make sure we do not cummulate images with same name
            self.removeItem(legend, replot=False)
        item = None
        shape = kw.get('shape', "polygon")
        if shape not in ['line', 'hline', 'vline', 'rectangle', 'polygon']:
            raise NotImplemented("Unsupported item shape %s" % shape)
        label = kw.get('label', legend)
        color = kw.get('color', 'black')
        fill = kw.get('fill', True)
        xView = numpy.asarray(x)
        yView = numpy.asarray(y)
        label = "__ITEM__" + label
        if shape in ["line", "hline", "vline"]:
            print("Not implemented")
            return legend
        elif shape in ["hline"]:
            if hasattr(y, "__len__"):
                y = y[-1]
            line = self.ax.axhline(y, label=label, color=color)
            return line
        elif shape in ["vline"]:
            if hasattr(x, "__len__"):
                x = x[-1]
            line = self.ax.axvline(x, label=label, color=color)
            return line
        elif shape in ['rectangle']:
            xMin = nanmin(xView)
            xMax = nanmax(xView)
            yMin = nanmin(yView)
            yMax = nanmax(yView)
            w = xMax - xMin
            h = yMax - yMin
            item = Rectangle(xy=(xMin,yMin),
                             width=w,
                             height=h,
                             fill=False,
                             color=color)
            if fill:
                item.set_hatch('.')
        elif shape in ['polygon']:
            xView.shape = 1, -1
            yView.shape = 1, -1
            item = Polygon(numpyvstack((xView, yView)).T,
                            closed=True,
                            fill=False,
                            label=label,
                            color=color)
            if fill:
                #item.set_hatch('+')
                item.set_hatch('/')
        if item is None:
            print("Undefined item")
            print("shape = ", shape)
            print("legend = ", legend)
            return
        self.ax.add_patch(item)
        if replot:
            self.ax.figure.canvas.draw()
        return item

    def clear(self):
        """
        Clear all curvers and other items from the plot
        """
        n = list(range(len(self.ax.lines)))
        n.reverse()
        for i in n:
            line2d = self.ax.lines[i]
            line2d.remove()
            del line2d
        self.ax.clear()

    def clearImages(self):
        n = list(range(len(self.ax.images)))
        n.reverse()
        for i in n:
            image = self.ax.images[i]
            image.remove()
            del image
            # in versions of matplotlib prior to 3.5.0
            # the content of the if block was needed
            if i < len(self.ax.images):
                del self.ax.images[i]

        n = list(range(len(self.ax.artists)))
        n.reverse()
        for i in n:
            artist = self.ax.artists[i]
            label = artist.get_label()
            if label.startswith("__IMAGE__"):
                artist.remove()
                del artist

    def clearCurves(self):
        """
        Clear all curves from the plot. Not the markers!!
        """
        for axes in [self.ax, self.ax2]:
            n = list(range(len(axes.lines)))
            n.reverse()
            for i in n:
                line2d = axes.lines[i]
                label = line2d.get_label()
                if label.startswith("__MARKER__"):
                    #it is a marker
                    continue
                line2d.remove()
                del line2d

    def clearMarkers(self):
        """
        Clear all markers from the plot. Not the curves!!
        """
        for axes in [self.ax, self.ax2]:
            n = list(range(len(axes.lines)))
            n.reverse()
            for i in n:
                line2d = axes.lines[i]
                label = line2d.get_label()
                if label.startswith("__MARKER__"):
                    #it is a marker
                    if hasattr(line2d, "_infoText"):
                        line2d._infoText.remove()
                    line2d.remove()
                    del line2d

    def clearItems(self):
        """
        Clear items, not markers, not curves
        """
        for axes in [self.ax, self.ax2]:
            n = list(range(len(axes.patches)))
            n.reverse()
            for i in n:
                item = axes.patches[i]
                label = item.get_label()
                if label.startswith("__ITEM__"):
                    item.remove()
                    del item

    def removeItem(self, handle, replot=True):
        if hasattr(handle, "remove"):
            for axes in [self.ax, self.ax2]:
                if handle in axes.patches:
                    handle.remove()
                    del handle
                    break
        else:
            # we have received a legend!
            # we have received a legend!
            legend = handle
            handle = None
            for axes in [self.ax, self.ax2]:
                if handle is not None:
                    break
                for item in axes.patches:
                    label = item.get_label()
                    if label == ("__ITEM__"+legend):
                        handle = item
                        break
            if handle is not None:
                handle.remove()
                del handle
        if replot:
            self.replot()

    def getGraphXLimits(self):
        """
        Get the graph X (bottom) limits.
        :return:  Minimum and maximum values of the X axis
        """
        vmin, vmax = self.ax.get_xlim()
        if vmin > vmax:
            return vmax, vmin
        else:
            return vmin, vmax

    def getGraphYLimits(self, axis="left"):
        if axis == "right":
            ax = self.ax2
        else:
            ax = self.ax
        vmin, vmax = ax.get_ylim()
        if vmin > vmax:
            return vmax, vmin
        else:
            return vmin, vmax

    def getWidgetHandle(self):
        """
        :return: Backend widget.
        """
        if hasattr(self.graph, "get_tk_widget"):
            return self.graph.get_tk_widget()
        else:
            return self.graph

    def enableAxis(self, axis, flag=True):
        """
        :param axis: Axis to be shown or hidden
        :type axis: string (left, right, top, bottom)
        :param flag: Boolean (default, True)
        """
        if axis == "right":
            self.ax2.get_yaxis().set_visible(flag)
            self._rightAxisEnabled = flag
        elif axis == "left":
            self.ax.get_yaxis().set_visible(flag)
        else:
            print("unhandled axis %s" % axis)

    def insertMarker(self, x, y, legend=None, text=None, color='k',
                      selectable=False, draggable=False, replot=True,
                      symbol=None, constraint=None,
                      **kw):
        """
        :param x: Horizontal position of the marker in graph coordenates
        :type x: float
        :param y: Vertical position of the marker in graph coordenates
        :type y: float
        :param label: Legend associated to the marker
        :type label: string
        :param color: Color to be used for instance 'blue', 'b', '#FF0000'
        :type color: string, default 'k' (black)
        :param selectable: Flag to indicate if the marker can be selected
        :type selectable: boolean, default False
        :param draggable: Flag to indicate if the marker can be moved
        :type draggable: boolean, default False
        :param replot: Flag to indicate if the plot is to be updated
        :type replot: boolean, default True
        :param str symbol: Symbol representing the marker
        :param constraint: None or a function filtering marker displacements.
        :return: Handle used by the backend to univocally access the marker
        """
        #line = self.ax.axvline(x, picker=True)
        xmin, xmax = self.getGraphXLimits()
        ymin, ymax = self.getGraphYLimits()
        if x is None:
            x = 0.5 * (xmax + xmin)
        if y is None:
            y = 0.5 * (ymax + ymin)

        # Apply constraint to provided position
        if draggable and constraint is not None:
            x, y = constraint(x, y)

        if legend is None:
            legend = "Unnamed marker"
        if text is None:
            text = kw.get("label", None)
            if text is not None:
                print("Deprecation warning: use 'text' instead of 'label'")
        self.removeMarker(legend, replot=False)

        legend = "__MARKER__" + legend
        if symbol is None:
            symbol = "+"
        markersize=10.
        if selectable or draggable:
            line = self.ax.plot(x, y, label=legend,
                                      linestyle=" ",
                                      color=color,
                                      picker=True,
                                      pickradius=5,
                                      marker=symbol,
                                      markersize=markersize)[-1]
        else:
            line = self.ax.plot(x, y, label=legend,
                                      linestyle=" ",
                                      color=color,
                                      picker=False,
                                      marker=symbol,
                                      markersize=markersize)[-1]
        if text is not None:
            xtmp, ytmp = self.ax.transData.transform((x, y))
            inv = self.ax.transData.inverted()
            xtmp, ytmp = inv.transform((xtmp, ytmp + 15))
            text = " " + text
            line._infoText = self.ax.text(x, ytmp, text,
                                          color=color,
                                          horizontalalignment='left',
                                          verticalalignment='top')

        line._constraint = constraint if draggable else None

        #line.set_ydata(numpy.array([1.0, 10.], dtype=numpy.float32))
        line._plot_options = ["marker"]
        if selectable:
            line._plot_options.append('selectable')
        if draggable:
            line._plot_options.append('draggable')
        if replot:
            self.replot()
        return line

    def insertXMarker(self, x, legend=None, text=None,
                      color='k', selectable=False, draggable=False,
                      replot=True, **kw):
        """
        :param x: Horizontal position of the marker in graph coordenates
        :type x: float
        :param legend: Legend associated to the marker
        :type legend: string
        :param label: Text associated to the marker
        :type label: string or None
        :param color: Color to be used for instance 'blue', 'b', '#FF0000'
        :type color: string, default 'k' (black)
        :param selectable: Flag to indicate if the marker can be selected
        :type selectable: boolean, default False
        :param draggable: Flag to indicate if the marker can be moved
        :type draggable: boolean, default False
        :param replot: Flag to indicate if the plot is to be updated
        :type replot: boolean, default True
        :return: Handle used by the backend to univocally access the marker
        """
        #line = self.ax.axvline(x, picker=True)
        if legend is None:
            legend = "Unnamed marker"
        if text is None:
            text = kw.get("label", None)
            if text is not None:
                print("Deprecation warning: use 'text' instead of 'label'")
        self.removeMarker(legend, replot=False)
        legend = "__MARKER__" + legend
        if selectable or draggable:
            line = self.ax.axvline(x, label=legend, color=color,
                                   picker=True, pickradius=5)
        else:
            line = self.ax.axvline(x, label=legend, color=color)
        if text is not None:
            text = " " + text
            ymin, ymax = self.getGraphYLimits()
            delta = abs(ymax - ymin)
            if ymin > ymax:
                ymax = ymin
            ymax -= 0.005 * delta
            line._infoText = self.ax.text(x, ymax, text,
                                          color=color,
                                          horizontalalignment='left',
                                          verticalalignment='top')
        #line.set_ydata(numpy.array([1.0, 10.], dtype=numpy.float32))
        line._plot_options = ["xmarker"]
        if selectable:
            line._plot_options.append('selectable')
        if draggable:
            line._plot_options.append('draggable')
        if replot:
            self.replot()
        return line

    def insertYMarker(self, y, legend=None, text=None,
                      color='k', selectable=False, draggable=False,
                      replot=True, **kw):
        """
        :param y: Vertical position of the marker in graph coordenates
        :type y: float
        :param legend: Legend associated to the marker
        :type legend: string
        :param label: Text associated to the marker
        :type label: string or None
        :param color: Color to be used for instance 'blue', 'b', '#FF0000'
        :type color: string, default 'k' (black)
        :param selectable: Flag to indicate if the marker can be selected
        :type selectable: boolean, default False
        :param draggable: Flag to indicate if the marker can be moved
        :type draggable: boolean, default False
        :param replot: Flag to indicate if the plot is to be updated
        :type replot: boolean, default True
        :return: Handle used by the backend to univocally access the marker
        """
        if legend is None:
            legend = "Unnamed marker"
        if text is None:
            text = kw.get("label", None)
            if text is not None:
                print("Deprecation warning: use 'text' instead of 'label'")
        legend = "__MARKER__" + legend
        if selectable or draggable:
            line = self.ax.axhline(y, label=legend, color=color,
                                   picker=True, pickradius=5)
        else:
            line = self.ax.axhline(y, label=legend, color=color)
        if text is not None:
            text = " " + text
            xmin, xmax = self.getGraphXLimits()
            delta = abs(xmax - xmin)
            if xmin > xmax:
                xmax = xmin
            xmax -= 0.005 * delta
            line._infoText = self.ax.text(y, xmax, text,
                                          color=color,
                                          horizontalalignment='left',
                                          verticalalignment='top')
        line._plot_options = ["ymarker"]
        if selectable:
            line._plot_options.append('selectable')
        if draggable:
            line._plot_options.append('draggable')
        if replot:
            self.replot()
        return line

    def isXAxisAutoScale(self):
        if self._xAutoScale:
            return True
        else:
            return False

    def isYAxisAutoScale(self):
        if self._yAutoScale:
            return True
        else:
            return False

    def removeCurve(self, handle, replot=True):
        if hasattr(handle, "remove"):
            if handle in self.ax.lines:
                handle.remove()
            if handle in self.ax2.lines:
                handle.remove()
            if handle in self.ax.collections:
                handle.remove()
            if handle in self.ax2.collections:
                handle.remove()
        else:
            # we have received a legend!
            legend = handle
            testLists = [self.ax.lines, self.ax2.lines,
                         self.ax.collections, self.ax2.collections]
            for container in testLists:
                for line2d in container:
                    handle = None
                    label = line2d.get_label()
                    if label == legend:
                        handle = line2d
                    if handle is not None:
                        handle.remove()
                        del handle
        if replot:
            self.replot()

    def removeImage(self, handle, replot=True):
        if hasattr(handle, "remove"):
            if (handle in self.ax.images) or (handle in self.ax.artists):
                handle.remove()
        else:
            # we have received a legend!
            legend = handle
            handle = None
            for item in self.ax.artists:
                label = item.get_label()
                if label == ("__IMAGE__" + legend):
                    handle = item
            if handle is None:
                for item in self.ax.images:
                    label = item.get_label()
                    if label == legend:
                        handle = item
            if handle is not None:
                handle.remove()
                del handle
        if replot:
            self.replot()

    def removeMarker(self, handle, replot=True):
        if hasattr(handle, "remove"):
            self._removeInfoText(handle)
            try:
                handle.remove()
            except ValueError:
                # it seems it was already removed
                pass
            del handle
        else:
            # we have received a legend!
            legend = handle
            done = False
            for axes in [self.ax, self.ax2]:
                for line2d in axes.lines:
                    if done:
                        break
                    label = line2d.get_label()
                    if label == ("__MARKER__" + legend):
                        if hasattr(line2d, "_infoText"):
                            line2d._infoText.remove()
                        line2d.remove()
                        del line2d
                        done = True
        if replot:
            self.replot()

    def _removeInfoText(self, handle):
        if hasattr(handle, "_infoText"):
            t = handle._infoText
            handle._infoText = None
            try:
                t.remove()
            except ValueError:
                # it seems it was already removed
                pass
            del t

    def resetZoom(self, dataMargins=None):
        """
        It should autoscale any axis that is in autoscale mode
        """
        xmin, xmax = self.getGraphXLimits()
        ymin, ymax = self.getGraphYLimits()
        xAuto = self.isXAxisAutoScale()
        yAuto = self.isYAxisAutoScale()
        if xAuto and yAuto:
            self.graph.resetZoom(dataMargins)
        elif yAuto:
            self.graph.resetZoom(dataMargins)
            self.setGraphXLimits(xmin, xmax)
        elif xAuto:
            self.graph.resetZoom(dataMargins)
            self.setGraphYLimits(ymin, ymax)
        else:
            if DEBUG:
                print("Nothing to autoscale")
        #xmin2, xmax2, ymin2, ymax2 = self.graph.getDataLimits('right')
        #self.ax2.figure.sca(self.ax2)
        #self.ax2.set_ylim(10., 100.)
        #self.ax2.figure.sca(self.ax)
        self._zoomStack = []

        self.replot()
        return

    def replot(self):
        """
        Update plot
        """
        if self._rightAxisEnabled is not None:
            # the right axis was initialized at a certain point
            # so, we have to see if there is something still mapped
            # to that axis.
            # For the time being we only check lines
            if not len(self.ax2.lines):
                self.enableAxis('right', False)
                self._rightAxisEnabled = None
        try:
            self.graph.draw()
        except ValueError:
            # TODO: Understand why matplotlib 2.1.0rc0 raises an
            # error when toggling semilogarithmic axes and previous
            # versions not.
            if DEBUG:
                print("MatplotlibBackend ERROR", sys.exc_info())
        return

    def saveGraph(self, fileName, fileFormat='svg', dpi=None , **kw):
        # fileName can be also a StringIO or file instance
        fig = self.ax.figure
        if dpi is not None:
            fig.savefig(fileName, format=fileFormat, dpi=dpi)
        else:
            fig.savefig(fileName, format=fileFormat)
        fig = None
        return

    def setActiveCurve(self, legend, replot=True):
        if not self._activeCurveHandling:
            return
        if hasattr(legend, "_plot_info"):
            # we have received an actual item
            handle = legend
        else:
            # we have received a legend
            handle = None
            for line2d in self.ax.lines:
                label = line2d.get_label()
                if label.startswith("__MARKER__"):
                    continue
                if label == legend:
                    handle = line2d
                    axes = self.ax
                    break
            if handle is None:
                for line2d in self.ax2.lines:
                    label = line2d.get_label()
                    if label.startswith("__MARKER__"):
                        continue
                    if label == legend:
                        handle = line2d
                        axes = self.ax2
                        break
        if handle is not None:
            handle.set_color(self._activeCurveColor)
        else:
            raise KeyError("Curve %s not found" % legend)
        if self._oldActiveCurve in self.ax.lines:
            if self._oldActiveCurve._plot_info['label'] != legend:
                color = self._oldActiveCurve._plot_info['color']
                self._oldActiveCurve.set_color(color)
        elif self._oldActiveCurve in self.ax2.lines:
            if self._oldActiveCurve._plot_info['label'] != legend:
                color = self._oldActiveCurve._plot_info['color']
                self._oldActiveCurve.set_color(color)
        elif self._oldActiveCurveLegend is not None:
            if self._oldActiveCurveLegend != handle._plot_info['label']:
                done = False
                for line2d in self.ax.lines:
                    label = line2d.get_label()
                    if label == self._oldActiveCurveLegend:
                        color = line2d._plot_info['color']
                        line2d.set_color(color)
                        done = True
                        break
                if not done:
                    for line2d in self.ax2.lines:
                        label = line2d.get_label()
                        if label == self._oldActiveCurveLegend:
                            color = line2d._plot_info['color']
                            line2d.set_color(color)
                            break
        #update labels according to active curve???
        if hasattr(handle, "_plot_info"):
            xLabel = handle._plot_info.get("xlabel", None)
            yLabel = handle._plot_info.get("ylabel", None)
            if (xLabel is not None) and (yLabel is not None):
                axes.set_xlabel(xLabel)
                axes.set_ylabel(yLabel)
        self._oldActiveCurve = handle
        self._oldActiveCurveLegend = handle.get_label()
        if replot:
            self.replot()

    def setCallback(self, callbackFunction):
        self.graph.setCallback(callbackFunction)
        # Should I call the base to keep a copy?
        # It does not seem necessary since the graph will do it.

    def getGraphTitle(self):
        return self.ax.get_title()

    def getGraphXLabel(self):
        return self.ax.get_xlabel()

    def getGraphYLabel(self):
        return self.ax.get_ylabel()

    def setGraphTitle(self, title=""):
        self.ax.set_title(title)

    def setGraphXLabel(self, label="X"):
        self.ax.set_xlabel(label)

    def setGraphXLimits(self, xmin, xmax):
        if xmin == xmax:
            delta = 0.044
            xmax += delta
            xmin -= delta
        self.ax.set_xlim(xmin, xmax)
        self.graph.emitLimitsChangedSignal()

    def setGraphYLabel(self, label="Y"):
        self.ax.set_ylabel(label)

    def setGraphYLimits(self, ymin, ymax):
        if ymin == ymax:
            delta = 0.044
            ymax += delta
            ymin -= delta
        if self.ax.yaxis_inverted():
            self.ax.set_ylim(ymax, ymin)
        else:
            self.ax.set_ylim(ymin, ymax)
        self.graph.emitLimitsChangedSignal()

    def setLimits(self, xmin, xmax, ymin, ymax):
        # Overrides PlotBackend to send a single limitsChanged event.
        self.ax.set_xlim(xmin, xmax)
        if self.ax.yaxis_inverted():
            self.ax.set_ylim(ymax, ymin)
        else:
            self.ax.set_ylim(ymin, ymax)
        self.graph.emitLimitsChangedSignal()

    def setXAxisAutoScale(self, flag=True):
        if flag:
            self._xAutoScale = True
        else:
            self._xAutoScale = False

    def setXAxisLogarithmic(self, flag):
        if flag:
            self._logX = True
            if hasattr(self.ax2, "get_visible"):
                if self.ax2.get_visible():
                    self.ax2.set_xscale('log')
            self.ax.set_xscale('log')
        else:
            self._logX = False
            if hasattr(self.ax2, "get_visible"):
                if self.ax2.get_visible():
                    self.ax2.set_xscale('linear')
            self.ax.set_xscale('linear')

    def setYAxisAutoScale(self, flag=True):
        if flag:
            self._yAutoScale = True
        else:
            self._yAutoScale = False

    def setYAxisLogarithmic(self, flag):
        """
        :param flag: If True, the left axis will use a log scale
        :type flag: boolean
        """
        if flag:
            self._logY = True
            if hasattr(self.ax2, "get_visible"):
                if self.ax2.get_visible():
                    self.ax2.set_yscale('log')
            self.ax.set_yscale('log')
        else:
            self._logY = False
            if hasattr(self.ax2, "get_visible"):
                if self.ax2.get_visible():
                    self.ax2.set_yscale('linear')
            self.ax.set_yscale('linear')

    def addImage(self, data, legend=None, info=None,
                    replace=True, replot=True,
                    xScale=None, yScale=None, z=0,
                    selectable=False, draggable=False,
                    colormap=None, **kw):
        """
        :param data: (nrows, ncolumns) data or (nrows, ncolumns, RGBA) ubyte array
        :type data: numpy.ndarray
        :param legend: The legend to be associated to the curve
        :type legend: string or None
        :param info: Dictionary of information associated to the image
        :type info: dict or None
        :param replace: Flag to indicate if already existing images are to be deleted
        :type replace: boolean default True
        :param replot: Flag to indicate plot is to be immediately updated
        :type replot: boolean default True
        :param xScale: Two floats defining the x scale
        :type xScale: list or numpy.ndarray
        :param yScale: Two floats defining the y scale
        :type yScale: list or numpy.ndarray
        :param z: level at which the image is to be located (to allow overlays).
        :type z: A number bigger than or equal to zero (default)
        :param selectable: Flag to indicate if the image can be selected
        :type selectable: boolean, default False
        :param draggable: Flag to indicate if the image can be moved
        :type draggable: boolean, default False
        :param colormap: Dictionary describing the colormap to use (or None)
        :type colormap: Dictionary or None (default). Ignored if data is RGB(A)
        :returns: The legend/handle used by the backend to univocally access it.
        """
        # Non-uniform image
        #http://wiki.scipy.org/Cookbook/Histograms
        # Non-linear axes
        #http://stackoverflow.com/questions/11488800/non-linear-axes-for-imshow-in-matplotlib
        if legend is None:
            legend = 'Unnamed image'

        if replace:
            self.clearImages()
        else:
            # make sure we do not cummulate images with same name
            self.removeImage(legend, replot=False)

        if xScale is None:
            xScale = [0.0, 1.0]
        if yScale is None:
            yScale = [0.0, 1.0]
        h, w = data.shape[0:2]
        xmin = xScale[0]
        xmax = xmin + xScale[1] * w
        ymin = yScale[0]
        ymax = ymin + yScale[1] * h
        extent = (xmin, xmax, ymax, ymin)

        if selectable or draggable:
            picker = True
        else:
            picker = False
        shape = data.shape
        if 0:
            # this supports non regularly spaced coordenates!!!!
            x = xmin + numpy.arange(w) * xScale[1]
            y = ymin + numpy.arange(h) * yScale[1]
            image = NonUniformImage(self.ax,
                                    interpolation='nearest',
                                    #aspect='auto',
                                    extent=extent,
                                    picker=picker,
                                    cmap=cmap)



            image.set_data(x, y, data)
            xmin, xmax = self.getGraphXLimits()
            ymin, ymax = self.getGraphYLimits()
            self.ax.images.append(image)
            self.ax.set_xlim(xmin, xmax)
            self.ax.set_ylim(ymin, ymax)
        elif 1:
            #the normalization can be a source of time waste
            # Two possibilities, we receive data or a ready to show image
            if len(data.shape) == 3:
                if data.shape[-1] == 4:
                    # force alpha(?)
                    # data[:,:,3] = 255
                    pass
            if len(shape) == 3:
                # RGBA image
                # TODO: Possibility to mirror the image
                # in case of pixmaps just setting
                # extend = (xmin, xmax, ymax, ymin)
                # instead of (xmin, xmax, ymin, ymax)
                extent = (xmin, xmax, ymin, ymax)
                if tuple(xScale) != (0., 1.) or tuple(yScale) != (0., 1.):
                    # for the time being not properly handled
                    imageClass = AxesImage
                elif (shape[0] * shape[1]) > 5.0e5:
                    imageClass = ModestImage
                else:
                    imageClass = AxesImage
                image = imageClass(self.ax,
                              label="__IMAGE__"+legend,
                              interpolation='nearest',
                              picker=picker,
                              zorder=z)
                if image.origin == 'upper':
                    image.set_extent((xmin, xmax, ymax, ymin))
                else:
                    image.set_extent((xmin, xmax, ymin, ymax))
                image.set_data(data)
            else:
                if colormap is None:
                    colormap = self.getDefaultColormap()
                cmap = self.__getColormap(colormap['name'])
                if colormap['normalization'].startswith('log'):
                    vmin, vmax = None, None
                    if not colormap['autoscale']:
                        if colormap['vmin'] > 0.:
                            vmin = colormap['vmin']
                        if colormap['vmax'] > 0.:
                            vmax = colormap['vmax']

                        if vmin is None or vmax is None:
                            print('Warning: ' +
                                  'Log colormap with negative bounds, ' +
                                  'changing bounds to positive ones.')
                        elif vmin > vmax:
                            print('Warning: Colormap bounds are inverted.')
                            vmin, vmax = vmax, vmin

                    # Set unset/negative bounds to positive bounds
                    if vmin is None or vmax is None:
                        posData = data[data > 0]
                        if vmax is None:
                            # 1. as an ultimate fallback
                            vmax = posData.max() if posData.size > 0 else 1.
                        if vmin is None:
                            vmin = posData.min() if posData.size > 0 else vmax
                        if vmin > vmax:
                            vmin = vmax

                    norm = LogNorm(vmin, vmax)

                else:  # Linear normalization
                    if colormap['autoscale']:
                        vmin = data.min()
                        vmax = data.max()
                    else:
                        vmin = colormap['vmin']
                        vmax = colormap['vmax']
                        if vmin > vmax:
                            print('Warning: Colormap bounds are inverted.')
                            vmin, vmax = vmax, vmin

                    norm = Normalize(vmin, vmax)

                # try as data
                if tuple(xScale) != (0., 1.) or tuple(yScale) != (0., 1.):
                    # for the time being not properly handled
                    imageClass = AxesImage
                elif (shape[0] * shape[1]) > 5.0e5:
                    imageClass = ModestImage
                else:
                    imageClass = AxesImage
                image = imageClass(self.ax,
                              label="__IMAGE__"+legend,
                              interpolation='nearest',
                              #origin=
                              cmap=cmap,
                              extent=extent,
                              picker=picker,
                              zorder=z,
                              norm=norm)
                if image.origin == 'upper':
                    image.set_extent((xmin, xmax, ymax, ymin))
                else:
                    image.set_extent((xmin, xmax, ymin, ymax))
                image.set_data(data)
            self.ax.add_artist(image)
            #self.ax.draw_artist(image)
        image._plot_info = {'label':legend,
                            'type':'image',
                            'xScale':xScale,
                            'yScale':yScale,
                            'z':z}
        image._plot_options = []
        if draggable:
            image._plot_options.append('draggable')
        if selectable:
            image._plot_options.append('selectable')
        return image

    def invertYAxis(self, flag=True):
        if flag:
            if not self.ax.yaxis_inverted():
                self.ax.invert_yaxis()
        else:
            if self.ax.yaxis_inverted():
                self.ax.invert_yaxis()

    def isYAxisInverted(self):
        return self.ax.yaxis_inverted()

    def showGrid(self, flag=True):
        if flag == 1:
            if hasattr(self.ax.xaxis, "set_tick_params"):
                self.ax.xaxis.set_tick_params(which='major')
                self.ax.yaxis.set_tick_params(which='major')
            self.ax.grid(which='major')
        elif flag == 2:
            if hasattr(self.ax.xaxis, "set_tick_params"):
                self.ax.xaxis.set_tick_params(which='both')
                self.ax.yaxis.set_tick_params(which='both')
            self.ax.grid(which='both')
        elif flag:
            if hasattr(self.ax.xaxis, "set_tick_params"):
                self.ax.xaxis.set_tick_params(which='major')
                self.ax.yaxis.set_tick_params(which='major')
            self.ax.grid(True)
        else:
            self.ax.grid(False)
        self.replot()

    def keepDataAspectRatio(self, flag=True):
        """
        :param flag:  True to respect data aspect ratio
        :type flag: Boolean, default True
        """
        if flag:
            for axes in [self.ax]:
                if axes.get_aspect() not in [1.0]:
                    axes.set_aspect(1.0)
                    self.resetZoom()
        else:
            for axes in [self.ax]:
                if axes.get_aspect() not in ['auto', None]:
                    axes.set_aspect('auto')
                    self.resetZoom()

    def isKeepDataAspectRatio(self):
        return self.ax.get_aspect() in (1.0, 'equal')

    def setDefaultColormap(self, colormap=None):
        if colormap is None:
            colormap = {'name': 'gray', 'normalization':'linear',
                        'autoscale':True, 'vmin':0.0, 'vmax':1.0,
                        'colors':256}
        self._defaultColormap = colormap

    def getDefaultColormap(self):
        if not hasattr(self, "_defaultColormap"):
            self.setDefaultColormap(None)
        return self._defaultColormap

    def getSupportedColormaps(self):
        default = ['gray', 'reversed gray', 'temperature', 'red', 'green', 'blue']
        maps = [m for m in cm.datad]
        maps.sort()
        return default + maps

    def __getColormap(self, name):
        if not hasattr(self, "__temperatureCmap"):
            #initialize own colormaps
            cdict = {'red': ((0.0, 0.0, 0.0),
                             (1.0, 1.0, 1.0)),
                     'green': ((0.0, 0.0, 0.0),
                               (1.0, 0.0, 0.0)),
                     'blue': ((0.0, 0.0, 0.0),
                              (1.0, 0.0, 0.0))}
            self.__redCmap = LinearSegmentedColormap('red',cdict,256)

            cdict = {'red': ((0.0, 0.0, 0.0),
                             (1.0, 0.0, 0.0)),
                     'green': ((0.0, 0.0, 0.0),
                               (1.0, 1.0, 1.0)),
                     'blue': ((0.0, 0.0, 0.0),
                              (1.0, 0.0, 0.0))}
            self.__greenCmap = LinearSegmentedColormap('green',cdict,256)

            cdict = {'red': ((0.0, 0.0, 0.0),
                             (1.0, 0.0, 0.0)),
                     'green': ((0.0, 0.0, 0.0),
                               (1.0, 0.0, 0.0)),
                     'blue': ((0.0, 0.0, 0.0),
                              (1.0, 1.0, 1.0))}
            self.__blueCmap = LinearSegmentedColormap('blue',cdict,256)

            # Temperature as defined in spslut
            cdict = {'red': ((0.0, 0.0, 0.0),
                             (0.5, 0.0, 0.0),
                             (0.75, 1.0, 1.0),
                             (1.0, 1.0, 1.0)),
                     'green': ((0.0, 0.0, 0.0),
                               (0.25, 1.0, 1.0),
                               (0.75, 1.0, 1.0),
                               (1.0, 0.0, 0.0)),
                     'blue': ((0.0, 1.0, 1.0),
                              (0.25, 1.0, 1.0),
                              (0.5, 0.0, 0.0),
                              (1.0, 0.0, 0.0))}
            #but limited to 256 colors for a faster display (of the colorbar)
            self.__temperatureCmap = LinearSegmentedColormap('temperature',
                                                             cdict, 256)

            #reversed gray
            cdict = {'red':     ((0.0, 1.0, 1.0),
                                 (1.0, 0.0, 0.0)),
                     'green':   ((0.0, 1.0, 1.0),
                                 (1.0, 0.0, 0.0)),
                     'blue':    ((0.0, 1.0, 1.0),
                                 (1.0, 0.0, 0.0))}

            self.__reversedGrayCmap = LinearSegmentedColormap('yerg', cdict, 256)

        if name == "reversed gray":
            return self.__reversedGrayCmap
        elif name == "temperature":
            return self.__temperatureCmap
        elif name == "red":
            return self.__redCmap
        elif name == "green":
            return self.__greenCmap
        elif name == "blue":
            return self.__blueCmap
        else:
            # built in
            return cm.get_cmap(name)

    def dataToPixel(self, x=None, y=None, axis="left"):
        """
        Convert a position in data space to a position in pixels in the widget.

        :param float x: The X coordinate in data space. If None (default)
                            the middle position of the displayed data is used.
        :param float y: The Y coordinate in data space. If None (default)
                            the middle position of the displayed data is used.
        :param str axis: The Y axis to use for the conversion
                         ('left' or 'right').
        :returns: The corresponding position in pixels or
                  None if the data position is not in the displayed area.
        :rtype: A tuple of 2 floats: (xPixel, yPixel) or None.
        """
        assert axis in ("left", "right")
        if "axis" == "right":
            ax = self.ax2
        else:
            ax = self.ax
        xmin, xmax = self.getGraphXLimits()
        ymin, ymax = self.getGraphYLimits(axis=axis)

        if x is None:
            x = 0.5 * (xmax - xmin)
        if y is None:
            y = 0.5 * (ymax - ymin)

        if (x > xmax)  or (x < xmin):
            return None

        if (y > ymax)  or (y < ymin):
            return None

        pixels = ax.transData.transform([x, y])
        xPixel, yPixel = pixels.T
        return xPixel, yPixel

    def pixelToData(self, x=None, y=None, axis="left"):
        assert axis in ("left", "right")
        if "axis" == "right":
            ax = self.ax2
        else:
            ax = self.ax
        inv = ax.transData.inverted()
        x, y = inv.transform((x, y))

        xmin, xmax = self.getGraphXLimits()
        ymin, ymax = self.getGraphYLimits(axis=axis)

        if (x > xmax)  or (x < xmin):
            return None

        if (y > ymax)  or (y < ymin):
            return None

        return x, y

def main(parent=None):
    from .. import Plot
    x = numpy.arange(100.)
    y = x * x
    plot = Plot.Plot(parent, backend=MatplotlibBackend)
    plot.addCurve(x, y, "dummy")
    plot.addCurve(x + 100, -x * x, "To set Active")
    #info = {}
    #info['plot_yaxis'] = 'right'
    #plot.addCurve(x + 100, -x * x + 500, "RIGHT", info=info)
    #print("Active curve = ", plot.getActiveCurve())
    print("X Limits) = ", plot.getGraphXLimits())
    print("Y Limits = ", plot.getGraphYLimits())
    #print("All curves = ", plot.getAllCurves())
    #plot.removeCurve("dummy")
    plot.setActiveCurve("To set Active")
    #print("All curves = ", plot.getAllCurves())
    #plot.resetZoom()
    return plot

if __name__ == "__main__":
    if ("tkinter" in sys.modules) or ("Tkinter" in sys.modules):
        root = Tk.Tk()
        parent=root
        #w = MatplotlibGraph(root)
        #Tk.mainloop()
        #sys.exit(0)
        w = main(parent)
        widget = w._plot.graph
    else:
        app = QtGui.QApplication([])
        parent = None
        w = main(parent)
        widget = w.getWidgetHandle()
    #w.invertYAxis(True)
    w.replot()
    #w.invertYAxis(True)
    data = numpy.arange(1000.*1000)
    data.shape = 10000,100
    #plot.replot()
    #w.invertYAxis(True)
    #w.replot()
    #w.widget.show()
    w.addImage(data, legend="image 0", xScale=(25, 1.0) , yScale=(-1000, 1.0),
                  selectable=True)
    w.removeImage("image 0")
    #w.invertYAxis(True)
    #w.replot()
    w.addImage(data, legend="image 1", xScale=(25, 1.0) , yScale=(-1000, 1.0),
                  replot=False, selectable=True)
    #w.invertYAxis(True)
    widget.ax.axis('auto') # appropriate for curves, no aspect ratio
    #w.widget.ax.axis('equal') # candidate for keepting aspect ratio
    #w.widget.ax.axis('scaled') # candidate for keepting aspect ratio
    w.insertXMarker(50., text="Label", color='pink', draggable=True)
    w.insertMarker(25, -5000, text="Label\n", color='pink', draggable=True)
    w.resetZoom()
    #print(w.widget.ax.get_images())
    #print(w.widget.ax.get_lines())
    if ("tkinter" in sys.modules) or ("Tkinter" in sys.modules):
        tkWidget = w.getWidgetHandle()
        tkWidget.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
        Tk.mainloop()
    else:
        widget.show()
        app.exec()
