"""
GUI Neutral widgets

All of these widgets require you to predefine an Axes instance and
pass that as the first arg.  matplotlib doesn't try to be too smart in
layout -- you have to figure out how wide and tall you want your Axes
to be to accommodate your widget.
"""

import numpy as np

from mlab import dist
from patches import Circle, Rectangle
from lines import Line2D
from transforms import blended_transform_factory

class LockDraw:
    """
    some widgets, like the cursor, draw onto the canvas, and this is not
    desirable under all circumstaces, like when the toolbar is in
    zoom-to-rect mode and drawing a rectangle.  The module level "lock"
    allows someone to grab the lock and prevent other widgets from
    drawing.  Use matplotlib.widgets.lock(someobj) to pr
    """
    def __init__(self):
        self._owner = None

    def __call__(self, o):
        'reserve the lock for o'
        if not self.available(o):
            raise ValueError('already locked')
        self._owner = o

    def release(self, o):
        'release the lock'
        if not self.available(o):
            raise ValueError('you do not own this lock')
        self._owner = None

    def available(self, o):
        'drawing is available to o'
        return not self.locked() or self.isowner(o)

    def isowner(self, o):
        'o owns the lock'
        return self._owner is o

    def locked(self):
        'the lock is held'
        return self._owner is not None

class Widget:
    """
    OK, I couldn't resist; abstract base class for mpl GUI neutral
    widgets
    """
    drawon = True
    eventson = True

class Button(Widget):
    """
    A GUI neutral button

    The following attributes are accesible

      ax    - the Axes the button renders into
      label - a text.Text instance
      color - the color of the button when not hovering
      hovercolor - the color of the button when hovering

    Call "on_clicked" to connect to the button
    """

    def __init__(self, ax, label, image=None,
                 color='0.85', hovercolor='0.95'):
        """
        ax is the Axes instance the button will be placed into

        label is a string which is the button text

        image if not None, is an image to place in the button -- can
          be any legal arg to imshow (numpy array, matplotlib Image
          instance, or PIL image)

        color is the color of the button when not activated

        hovercolor is the color of the button when the mouse is over
          it

        """
        if image is not None:
            ax.imshow(image)
        self.label = ax.text(0.5, 0.5, label,
                             verticalalignment='center',
                             horizontalalignment='center',
                             transform=ax.transAxes)

        self.cnt = 0
        self.observers = {}
        self.ax = ax

        ax.figure.canvas.mpl_connect('button_press_event', self._click)
        ax.figure.canvas.mpl_connect('motion_notify_event', self._motion)
        ax.set_navigate(False)
        ax.set_axis_bgcolor(color)
        ax.set_xticks([])
        ax.set_yticks([])
        self.color = color
        self.hovercolor = hovercolor

        self._lastcolor = color

    def _click(self, event):
        if event.inaxes != self.ax: return
        if not self.eventson: return
        for cid, func in self.observers.items():
            func(event)

    def _motion(self, event):
        if event.inaxes==self.ax:
            c = self.hovercolor
        else:
            c = self.color
        if c != self._lastcolor:
            self.ax.set_axis_bgcolor(c)
            self._lastcolor = c
            if self.drawon: self.ax.figure.canvas.draw()

    def on_clicked(self, func):
        """
        When the button is clicked, call this func with event

        A connection id is returned which can be used to disconnect
        """
        cid = self.cnt
        self.observers[cid] = func
        self.cnt += 1
        return cid

    def disconnect(self, cid):
        'remove the observer with connection id cid'
        try: del self.observers[cid]
        except KeyError: pass

class Slider(Widget):
    """
    A slider representing a floating point range

    The following attributes are defined
      ax     : the slider axes.Axes instance
      val    : the current slider value
      vline  : a Line2D instance representing the initial value
      poly   : A patch.Polygon instance which is the slider
      valfmt : the format string for formatting the slider text
      label  : a text.Text instance, the slider label
      closedmin : whether the slider is closed on the minimum
      closedmax : whether the slider is closed on the maximum
      slidermin : another slider - if not None, this slider must be > slidermin
      slidermax : another slider - if not None, this slider must be < slidermax
      dragging : allow for mouse dragging on slider

    Call on_changed to connect to the slider event
    """
    def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f',
                 closedmin=True, closedmax=True, slidermin=None, slidermax=None,
                 dragging=True, **kwargs):
        """
        Create a slider from valmin to valmax in axes ax;

        valinit -  the slider initial position

        label - the slider label

        valfmt - used to format the slider value

        closedmin and closedmax - indicate whether the slider interval is closed

        slidermin and slidermax - be used to contrain the value of
          this slider to the values of other sliders.

        additional kwargs are passed on to self.poly which is the
        matplotlib.patches.Rectangle which draws the slider.  See the
        matplotlib.patches.Rectangle documentation for legal property
        names (eg facecolor, edgecolor, alpha, ...)
          """
        self.ax = ax

        self.valmin = valmin
        self.valmax = valmax
        self.val = valinit
        self.valinit = valinit
        self.poly = ax.axvspan(valmin,valinit,0,1, **kwargs)

        self.vline = ax.axvline(valinit,0,1, color='r', lw=1)

        self.valfmt=valfmt
        ax.set_yticks([])
        ax.set_xlim((valmin, valmax))
        ax.set_xticks([])
        ax.set_navigate(False)

        ax.figure.canvas.mpl_connect('button_press_event', self._update)
        if dragging:
            ax.figure.canvas.mpl_connect('motion_notify_event', self._update)
        self.label = ax.text(-0.02, 0.5, label, transform=ax.transAxes,
                             verticalalignment='center',
                             horizontalalignment='right')

        self.valtext = ax.text(1.02, 0.5, valfmt%valinit,
                               transform=ax.transAxes,
                               verticalalignment='center',
                               horizontalalignment='left')

        self.cnt = 0
        self.observers = {}

        self.closedmin = closedmin
        self.closedmax = closedmax
        self.slidermin = slidermin
        self.slidermax = slidermax

    def _update(self, event):
        'update the slider position'
        if event.button !=1: return
        if event.inaxes != self.ax: return
        val = event.xdata
        if not self.closedmin and val<=self.valmin: return
        if not self.closedmax and val>=self.valmax: return

        if self.slidermin is not None:
            if val<=self.slidermin.val: return

        if self.slidermax is not None:
            if val>=self.slidermax.val: return

        self.set_val(val)

    def set_val(self, val):
        xy = self.poly.xy
        xy[-1] = val, 0
        xy[-2] = val, 1
        self.poly.xy = xy
        self.valtext.set_text(self.valfmt%val)
        if self.drawon: self.ax.figure.canvas.draw()
        self.val = val
        if not self.eventson: return
        for cid, func in self.observers.items():
            func(val)

    def on_changed(self, func):
        """
        When the slider valud is changed, call this func with the new
        slider position

        A connection id is returned which can be used to disconnect
        """
        cid = self.cnt
        self.observers[cid] = func
        self.cnt += 1
        return cid

    def disconnect(self, cid):
        'remove the observer with connection id cid'
        try: del self.observers[cid]
        except KeyError: pass

    def reset(self):
        "reset the slider to the initial value if needed"
        if (self.val != self.valinit):
            self.set_val(self.valinit)

class CheckButtons(Widget):
    """
    A GUI neutral radio button

    The following attributes are exposed

     ax - the Axes instance the buttons are in
     labels - a list of text.Text instances
     lines - a list of (line1, line2) tuples for the x's in the check boxes.
             These lines exist for each box, but have set_visible(False) when
             box is not checked
     rectangles - a list of patch.Rectangle instances

    Connect to the CheckButtons with the on_clicked method
    """
    def __init__(self, ax, labels, actives):
        """
        Add check buttons to axes.Axes instance ax

        labels is a len(buttons) list of labels as strings

        actives is a len(buttons) list of booleans indicating whether
         the button is active

        """

        ax.set_xticks([])
        ax.set_yticks([])
        ax.set_navigate(False)

        if len(labels)>1:
            dy = 1./(len(labels)+1)
            ys = np.linspace(1-dy, dy, len(labels))
        else:
            dy = 0.25
            ys = [0.5]

        cnt = 0
        axcolor = ax.get_axis_bgcolor()

        self.labels = []
        self.lines = []
        self.rectangles = []

        lineparams = {'color':'k', 'linewidth':1.25, 'transform':ax.transAxes,
                      'solid_capstyle':'butt'}
        for y, label in zip(ys, labels):
            t = ax.text(0.25, y, label, transform=ax.transAxes,
                        horizontalalignment='left',
                        verticalalignment='center')

            w, h = dy/2., dy/2.
            x, y = 0.05, y-h/2.

            p = Rectangle(xy=(x,y), width=w, height=h,
                          facecolor=axcolor,
                          transform=ax.transAxes)

            l1 = Line2D([x, x+w], [y+h, y], **lineparams)
            l2 = Line2D([x, x+w], [y, y+h], **lineparams)

            l1.set_visible(actives[cnt])
            l2.set_visible(actives[cnt])
            self.labels.append(t)
            self.rectangles.append(p)
            self.lines.append((l1,l2))
            ax.add_patch(p)
            ax.add_line(l1)
            ax.add_line(l2)
            cnt += 1

        ax.figure.canvas.mpl_connect('button_press_event', self._clicked)
        self.ax = ax

        self.cnt = 0
        self.observers = {}

    def _clicked(self, event):
        if event.button !=1 : return
        if event.inaxes != self.ax: return

        for p,t,lines in zip(self.rectangles, self.labels, self.lines):
            if (t.get_window_extent().contains(event.x, event.y) or
                p.get_window_extent().contains(event.x, event.y) ):
                l1, l2 = lines
                l1.set_visible(not l1.get_visible())
                l2.set_visible(not l2.get_visible())
                thist = t
                break
        else:
            return

        if self.drawon: self.ax.figure.canvas.draw()

        if not self.eventson: return
        for cid, func in self.observers.items():
            func(thist.get_text())

    def on_clicked(self, func):
        """
        When the button is clicked, call this func with button label

        A connection id is returned which can be used to disconnect
        """
        cid = self.cnt
        self.observers[cid] = func
        self.cnt += 1
        return cid

    def disconnect(self, cid):
        'remove the observer with connection id cid'
        try: del self.observers[cid]
        except KeyError: pass

class RadioButtons(Widget):
    """
    A GUI neutral radio button

    The following attributes are exposed

     ax - the Axes instance the buttons are in
     activecolor - the color of the button when clicked
     labels - a list of text.Text instances
     circles - a list of patch.Circle instances

    Connect to the RadioButtons with the on_clicked method
    """
    def __init__(self, ax, labels, active=0, activecolor='blue'):
        """
        Add radio buttons to axes.Axes instance ax

        labels is a len(buttons) list of labels as strings

        active is the index into labels for the button that is active

        activecolor is the color of the button when clicked
        """
        self.activecolor = activecolor

        ax.set_xticks([])
        ax.set_yticks([])
        ax.set_navigate(False)
        dy = 1./(len(labels)+1)
        ys = np.linspace(1-dy, dy, len(labels))
        cnt = 0
        axcolor = ax.get_axis_bgcolor()

        self.labels = []
        self.circles = []
        for y, label in zip(ys, labels):
            t = ax.text(0.25, y, label, transform=ax.transAxes,
                        horizontalalignment='left',
                        verticalalignment='center')

            if cnt==active:
                facecolor = activecolor
            else:
                facecolor = axcolor

            p = Circle(xy=(0.15, y), radius=0.05, facecolor=facecolor,
                       transform=ax.transAxes)

            self.labels.append(t)
            self.circles.append(p)
            ax.add_patch(p)
            cnt += 1

        ax.figure.canvas.mpl_connect('button_press_event', self._clicked)
        self.ax = ax

        self.cnt = 0
        self.observers = {}

    def _clicked(self, event):
        if event.button !=1 : return
        if event.inaxes != self.ax: return
        xy = self.ax.transAxes.inverted().transform_point((event.x, event.y))
        pclicked = np.array([xy[0], xy[1]])
        def inside(p):
            pcirc = np.array([p.center[0], p.center[1]])
            return dist(pclicked, pcirc) < p.radius

        for p,t in zip(self.circles, self.labels):
            if t.get_window_extent().contains(event.x, event.y) or inside(p):
                inp = p
                thist = t
                break
        else: return

        for p in self.circles:
            if p==inp: color = self.activecolor
            else: color = self.ax.get_axis_bgcolor()
            p.set_facecolor(color)

        if self.drawon: self.ax.figure.canvas.draw()

        if not self.eventson: return
        for cid, func in self.observers.items():
            func(thist.get_text())

    def on_clicked(self, func):
        """
        When the button is clicked, call this func with button label

        A connection id is returned which can be used to disconnect
        """
        cid = self.cnt
        self.observers[cid] = func
        self.cnt += 1
        return cid

    def disconnect(self, cid):
        'remove the observer with connection id cid'
        try: del self.observers[cid]
        except KeyError: pass

class SubplotTool(Widget):
    """
    A tool to adjust to subplot params of fig
    """
    def __init__(self, targetfig, toolfig):
        """
        targetfig is the figure to adjust

        toolfig is the figure to embed the the subplot tool into.  If
        None, a default pylab figure will be created.  If you are
        using this from the GUI
        """
        self.targetfig = targetfig
        toolfig.subplots_adjust(left=0.2, right=0.9)

        class toolbarfmt:
            def __init__(self, slider):
                self.slider = slider

            def __call__(self, x, y):
                fmt = '%s=%s'%(self.slider.label.get_text(), self.slider.valfmt)
                return fmt%x

        self.axleft = toolfig.add_subplot(711)
        self.axleft.set_title('Click on slider to adjust subplot param')
        self.axleft.set_navigate(False)

        self.sliderleft = Slider(self.axleft, 'left', 0, 1, targetfig.subplotpars.left, closedmax=False)
        self.sliderleft.on_changed(self.funcleft)

        self.axbottom = toolfig.add_subplot(712)
        self.axbottom.set_navigate(False)
        self.sliderbottom = Slider(self.axbottom, 'bottom', 0, 1, targetfig.subplotpars.bottom, closedmax=False)
        self.sliderbottom.on_changed(self.funcbottom)

        self.axright = toolfig.add_subplot(713)
        self.axright.set_navigate(False)
        self.sliderright = Slider(self.axright, 'right', 0, 1, targetfig.subplotpars.right, closedmin=False)
        self.sliderright.on_changed(self.funcright)

        self.axtop = toolfig.add_subplot(714)
        self.axtop.set_navigate(False)
        self.slidertop = Slider(self.axtop, 'top', 0, 1, targetfig.subplotpars.top, closedmin=False)
        self.slidertop.on_changed(self.functop)

        self.axwspace = toolfig.add_subplot(715)
        self.axwspace.set_navigate(False)
        self.sliderwspace = Slider(self.axwspace, 'wspace', 0, 1, targetfig.subplotpars.wspace, closedmax=False)
        self.sliderwspace.on_changed(self.funcwspace)

        self.axhspace = toolfig.add_subplot(716)
        self.axhspace.set_navigate(False)
        self.sliderhspace = Slider(self.axhspace, 'hspace', 0, 1, targetfig.subplotpars.hspace, closedmax=False)
        self.sliderhspace.on_changed(self.funchspace)

        # constraints
        self.sliderleft.slidermax = self.sliderright
        self.sliderright.slidermin = self.sliderleft
        self.sliderbottom.slidermax = self.slidertop
        self.slidertop.slidermin = self.sliderbottom

        bax = toolfig.add_axes([0.8, 0.05, 0.15, 0.075])
        self.buttonreset = Button(bax, 'Reset')

        sliders = (self.sliderleft, self.sliderbottom, self.sliderright,
                   self.slidertop, self.sliderwspace, self.sliderhspace, )

        def func(event):
            thisdrawon = self.drawon

            self.drawon = False

            # store the drawon state of each slider
            bs = []
            for slider in sliders:
                bs.append(slider.drawon)
                slider.drawon = False

            # reset the slider to the initial position
            for slider in sliders:
                slider.reset()

            # reset drawon
            for slider, b in zip(sliders, bs):
                slider.drawon = b

            # draw the canvas
            self.drawon = thisdrawon
            if self.drawon:
                toolfig.canvas.draw()
                self.targetfig.canvas.draw()

        # during reset there can be a temporary invalid state
        # depending on the order of the reset so we turn off
        # validation for the resetting
        validate = toolfig.subplotpars.validate
        toolfig.subplotpars.validate = False
        self.buttonreset.on_clicked(func)
        toolfig.subplotpars.validate = validate

    def funcleft(self, val):
        self.targetfig.subplots_adjust(left=val)
        if self.drawon: self.targetfig.canvas.draw()

    def funcright(self, val):
        self.targetfig.subplots_adjust(right=val)
        if self.drawon: self.targetfig.canvas.draw()

    def funcbottom(self, val):
        self.targetfig.subplots_adjust(bottom=val)
        if self.drawon: self.targetfig.canvas.draw()

    def functop(self, val):
        self.targetfig.subplots_adjust(top=val)
        if self.drawon: self.targetfig.canvas.draw()

    def funcwspace(self, val):
        self.targetfig.subplots_adjust(wspace=val)
        if self.drawon: self.targetfig.canvas.draw()

    def funchspace(self, val):
        self.targetfig.subplots_adjust(hspace=val)
        if self.drawon: self.targetfig.canvas.draw()

class Cursor:
    """
    A horizontal and vertical line span the axes that and move with
    the pointer.  You can turn off the hline or vline spectively with
    the attributes

      horizOn =True|False: controls visibility of the horizontal line
      vertOn =True|False: controls visibility of the horizontal line

    And the visibility of the cursor itself with visible attribute
    """
    def __init__(self, ax, useblit=False, **lineprops):
        """
        Add a cursor to ax.  If useblit=True, use the backend
        dependent blitting features for faster updates (GTKAgg only
        now).  lineprops is a dictionary of line properties.  See
        examples/widgets/cursor.py.
        """
        self.ax = ax
        self.canvas = ax.figure.canvas

        self.canvas.mpl_connect('motion_notify_event', self.onmove)
        self.canvas.mpl_connect('draw_event', self.clear)

        self.visible = True
        self.horizOn = True
        self.vertOn = True
        self.useblit = useblit

        self.lineh = ax.axhline(ax.get_ybound()[0], visible=False, **lineprops)
        self.linev = ax.axvline(ax.get_xbound()[0], visible=False, **lineprops)

        self.background = None
        self.needclear = False

    def clear(self, event):
        'clear the cursor'
        if self.useblit:
            self.background = self.canvas.copy_from_bbox(self.ax.bbox)
        self.linev.set_visible(False)
        self.lineh.set_visible(False)

    def onmove(self, event):
        'on mouse motion draw the cursor if visible'
        if event.inaxes != self.ax:
            self.linev.set_visible(False)
            self.lineh.set_visible(False)

            if self.needclear:
                self.canvas.draw()
                self.needclear = False
            return
        self.needclear = True
        if not self.visible: return
        self.linev.set_xdata((event.xdata, event.xdata))

        self.lineh.set_ydata((event.ydata, event.ydata))
        self.linev.set_visible(self.visible and self.vertOn)
        self.lineh.set_visible(self.visible and self.horizOn)

        self._update()

    def _update(self):

        if self.useblit:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            self.ax.draw_artist(self.linev)
            self.ax.draw_artist(self.lineh)
            self.canvas.blit(self.ax.bbox)
        else:

            self.canvas.draw_idle()

        return False

class MultiCursor:
    """
    Provide a vertical line cursor shared between multiple axes

    from matplotlib.widgets import MultiCursor
    from pylab import figure, show, nx

    t = nx.arange(0.0, 2.0, 0.01)
    s1 = nx.sin(2*nx.pi*t)
    s2 = nx.sin(4*nx.pi*t)
    fig = figure()
    ax1 = fig.add_subplot(211)
    ax1.plot(t, s1)

    ax2 = fig.add_subplot(212, sharex=ax1)
    ax2.plot(t, s2)

    multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1)
    show()

    """
    def __init__(self, canvas, axes, useblit=True, **lineprops):
        self.canvas = canvas
        self.axes = axes
        xmin, xmax = axes[-1].get_xlim()
        xmid = 0.5*(xmin+xmax)
        self.lines = [ax.axvline(xmid, visible=False, **lineprops) for ax in axes]

        self.visible = True
        self.useblit = useblit
        self.background = None
        self.needclear = False

        self.canvas.mpl_connect('motion_notify_event', self.onmove)
        self.canvas.mpl_connect('draw_event', self.clear)

    def clear(self, event):
        'clear the cursor'
        if self.useblit:
            self.background = self.canvas.copy_from_bbox(self.canvas.figure.bbox)
        for line in self.lines: line.set_visible(False)

    def onmove(self, event):
        if event.inaxes is None: return
        if not self.canvas.widgetlock.available(self): return
        self.needclear = True
        if not self.visible: return

        for line in self.lines:
            line.set_xdata((event.xdata, event.xdata))
            line.set_visible(self.visible)
        self._update()

    def _update(self):

        if self.useblit:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            for ax, line in zip(self.axes, self.lines):
                ax.draw_artist(line)
            self.canvas.blit(self.canvas.figure.bbox)
        else:

            self.canvas.draw_idle()

class SpanSelector:
    """
    Select a min/max range of the x or y axes for a matplotlib Axes

    Example usage:

      ax = subplot(111)
      ax.plot(x,y)

      def onselect(vmin, vmax):
          print vmin, vmax
      span = SpanSelector(ax, onselect, 'horizontal')

      onmove_callback is an optional callback that will be called on mouse move
      with the span range

    """

    def __init__(self, ax, onselect, direction, minspan=None, useblit=False, rectprops=None, onmove_callback=None):
        """
        Create a span selector in ax.  When a selection is made, clear
        the span and call onselect with

          onselect(vmin, vmax)

        and clear the span.

        direction must be 'horizontal' or 'vertical'

        If minspan is not None, ignore events smaller than minspan

        The span rect is drawn with rectprops; default
          rectprops = dict(facecolor='red', alpha=0.5)

        set the visible attribute to False if you want to turn off
        the functionality of the span selector

        """
        if rectprops is None:
            rectprops = dict(facecolor='red', alpha=0.5)

        assert direction in ['horizontal', 'vertical'], 'Must choose horizontal or vertical for direction'
        self.direction = direction

        self.ax = None
        self.canvas = None
        self.visible = True
        self.cids=[]

        self.rect = None
        self.background = None
        self.pressv = None

        self.rectprops = rectprops
        self.onselect = onselect
        self.onmove_callback = onmove_callback
        self.useblit = useblit
        self.minspan = minspan

        # Needed when dragging out of axes
        self.buttonDown = False
        self.prev = (0, 0)

        self.new_axes(ax)

    def new_axes(self,ax):
        self.ax = ax
        if self.canvas is not ax.figure.canvas:
            for cid in self.cids:
                self.canvas.mpl_disconnect(cid)

            self.canvas = ax.figure.canvas

            self.cids.append(self.canvas.mpl_connect('motion_notify_event', self.onmove))
            self.cids.append(self.canvas.mpl_connect('button_press_event', self.press))
            self.cids.append(self.canvas.mpl_connect('button_release_event', self.release))
            self.cids.append(self.canvas.mpl_connect('draw_event', self.update_background))
        if self.direction == 'horizontal':
            trans = blended_transform_factory(self.ax.transData, self.ax.transAxes)
            w,h = 0,1
        else:
            trans = blended_transform_factory(self.ax.transAxes, self.ax.transData)
            w,h = 1,0
        self.rect = Rectangle( (0,0), w, h,
                               transform=trans,
                               visible=False,
                               **self.rectprops
                               )

        if not self.useblit: self.ax.add_patch(self.rect)

    def update_background(self, event):
        'force an update of the background'
        if self.useblit:
            self.background = self.canvas.copy_from_bbox(self.ax.bbox)

    def ignore(self, event):
        'return True if event should be ignored'
        return  event.inaxes!=self.ax or not self.visible or event.button !=1

    def press(self, event):
        'on button press event'
        if self.ignore(event): return
        self.buttonDown = True

        self.rect.set_visible(self.visible)
        if self.direction == 'horizontal':
            self.pressv = event.xdata
        else:
            self.pressv = event.ydata
        return False

    def release(self, event):
        'on button release event'
        if self.pressv is None or (self.ignore(event) and not self.buttonDown): return
        self.buttonDown = False

        self.rect.set_visible(False)
        self.canvas.draw()
        vmin = self.pressv
        if self.direction == 'horizontal':
            vmax = event.xdata or self.prev[0]
        else:
            vmax = event.ydata or self.prev[1]

        if vmin>vmax: vmin, vmax = vmax, vmin
        span = vmax - vmin
        if self.minspan is not None and span<self.minspan: return
        self.onselect(vmin, vmax)
        self.pressv = None
        return False

    def update(self):
        'draw using newfangled blit or oldfangled draw depending on useblit'
        if self.useblit:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            self.ax.draw_artist(self.rect)
            self.canvas.blit(self.ax.bbox)
        else:
            self.canvas.draw_idle()

        return False

    def onmove(self, event):
        'on motion notify event'
        if self.pressv is None or self.ignore(event): return
        x, y = event.xdata, event.ydata
        self.prev = x, y
        if self.direction == 'horizontal':
            v = x
        else:
            v = y

        minv, maxv = v, self.pressv
        if minv>maxv: minv, maxv = maxv, minv
        if self.direction == 'horizontal':
            self.rect.set_x(minv)
            self.rect.set_width(maxv-minv)
        else:
            self.rect.set_y(minv)
            self.rect.set_height(maxv-minv)

        if self.onmove_callback is not None:
            vmin = self.pressv
            if self.direction == 'horizontal':
                vmax = event.xdata or self.prev[0]
            else:
                vmax = event.ydata or self.prev[1]

            if vmin>vmax: vmin, vmax = vmax, vmin
            self.onmove_callback(vmin, vmax)

        self.update()
        return False

# For backwards compatibility only!
class HorizontalSpanSelector(SpanSelector):
    def __init__(self, ax, onselect, **kwargs):
        import warnings
        warnings.warn('Use SpanSelector instead!', DeprecationWarning)
        SpanSelector.__init__(self, ax, onselect, 'horizontal', **kwargs)

class RectangleSelector:
    """
    Select a min/max range of the x axes for a matplotlib Axes

    Example usage::

        from matplotlib.widgets import  RectangleSelector
        from pylab import *

        def onselect(eclick, erelease):
          'eclick and erelease are matplotlib events at press and release'
          print ' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata)
          print ' endposition   : (%f, %f)' % (erelease.xdata, erelease.ydata)
          print ' used button   : ', eclick.button

        def toggle_selector(event):
            print ' Key pressed.'
            if event.key in ['Q', 'q'] and toggle_selector.RS.active:
                print ' RectangleSelector deactivated.'
                toggle_selector.RS.set_active(False)
            if event.key in ['A', 'a'] and not toggle_selector.RS.active:
                print ' RectangleSelector activated.'
                toggle_selector.RS.set_active(True)

        x = arange(100)/(99.0)
        y = sin(x)
        fig = figure
        ax = subplot(111)
        ax.plot(x,y)

        toggle_selector.RS = RectangleSelector(ax, onselect, drawtype='line')
        connect('key_press_event', toggle_selector)
        show()
    """
    def __init__(self, ax, onselect, drawtype='box',
                 minspanx=None, minspany=None, useblit=False,
                 lineprops=None, rectprops=None, spancoords='data'):

        """
        Create a selector in ax.  When a selection is made, clear
        the span and call onselect with

          onselect(pos_1, pos_2)

        and clear the drawn box/line. There pos_i are arrays of length 2
        containing the x- and y-coordinate.

        If minspanx is not None then events smaller than minspanx
        in x direction are ignored(it's the same for y).

        The rect is drawn with rectprops; default
          rectprops = dict(facecolor='red', edgecolor = 'black',
                           alpha=0.5, fill=False)

        The line is drawn with lineprops; default
          lineprops = dict(color='black', linestyle='-',
                           linewidth = 2, alpha=0.5)

        Use type if you want the mouse to draw a line, a box or nothing
        between click and actual position ny setting

        drawtype = 'line', drawtype='box' or drawtype = 'none'.

        spancoords is one of 'data' or 'pixels'.  If 'data', minspanx
        and minspanx will be interpreted in the same coordinates as
        the x and ya axis, if 'pixels', they are in pixels
        """
        self.ax = ax
        self.visible = True
        self.canvas = ax.figure.canvas
        self.canvas.mpl_connect('motion_notify_event', self.onmove)
        self.canvas.mpl_connect('button_press_event', self.press)
        self.canvas.mpl_connect('button_release_event', self.release)
        self.canvas.mpl_connect('draw_event', self.update_background)

        self.active = True                    # for activation / deactivation
        self.to_draw = None
        self.background = None

        if drawtype == 'none':
            drawtype = 'line'                        # draw a line but make it
            self.visible = False                     # invisible

        if drawtype == 'box':
            if rectprops is None:
                rectprops = dict(facecolor='white', edgecolor = 'black',
                                 alpha=0.5, fill=False)
            self.rectprops = rectprops
            self.to_draw = Rectangle((0,0), 0, 1,visible=False,**self.rectprops)
            self.ax.add_patch(self.to_draw)
        if drawtype == 'line':
            if lineprops is None:
                lineprops = dict(color='black', linestyle='-',
                                 linewidth = 2, alpha=0.5)
            self.lineprops = lineprops
            self.to_draw = Line2D([0,0],[0,0],visible=False,**self.lineprops)
            self.ax.add_line(self.to_draw)

        self.onselect = onselect
        self.useblit = useblit
        self.minspanx = minspanx
        self.minspany = minspany

        assert(spancoords in ('data', 'pixels'))

        self.spancoords = spancoords
        self.drawtype = drawtype
        # will save the data (position at mouseclick)
        self.eventpress = None
        # will save the data (pos. at mouserelease)
        self.eventrelease = None

    def update_background(self, event):
        'force an update of the background'
        if self.useblit:
            self.background = self.canvas.copy_from_bbox(self.ax.bbox)

    def ignore(self, event):
        'return True if event should be ignored'
        # If RectangleSelector is not active :
        if not self.active:
            return True

        # If canvas was locked
        if not self.canvas.widgetlock.available(self):
            return True

        # If no button was pressed yet ignore the event if it was out
        # of the axes
        if self.eventpress == None:
            return event.inaxes!= self.ax

        # If a button was pressed, check if the release-button is the
        # same.
        return  (event.inaxes!=self.ax or
                 event.button != self.eventpress.button)

    def press(self, event):
        'on button press event'
        # Is the correct button pressed within the correct axes?
        if self.ignore(event): return

        # make the drawed box/line visible get the click-coordinates,
        # button, ...
        self.to_draw.set_visible(self.visible)
        self.eventpress = event
        return False

    def release(self, event):
        'on button release event'
        if self.eventpress is None or self.ignore(event): return
        # make the box/line invisible again
        self.to_draw.set_visible(False)
        self.canvas.draw()
        # release coordinates, button, ...
        self.eventrelease = event

        if self.spancoords=='data':
            xmin, ymin = self.eventpress.xdata, self.eventpress.ydata
            xmax, ymax = self.eventrelease.xdata, self.eventrelease.ydata
            # calculate dimensions of box or line get values in the right
            # order
        elif self.spancoords=='pixels':
            xmin, ymin = self.eventpress.x, self.eventpress.y
            xmax, ymax = self.eventrelease.x, self.eventrelease.y
        else:
            raise ValueError('spancoords must be "data" or "pixels"')

        if xmin>xmax: xmin, xmax = xmax, xmin
        if ymin>ymax: ymin, ymax = ymax, ymin

        spanx = xmax - xmin
        spany = ymax - ymin
        xproblems = self.minspanx is not None and spanx<self.minspanx
        yproblems = self.minspany is not None and spany<self.minspany
        if (self.drawtype=='box')  and (xproblems or  yproblems):
            """Box to small"""     # check if drawed distance (if it exists) is
            return                 # not to small in neither x nor y-direction
        if (self.drawtype=='line') and (xproblems and yproblems):
            """Line to small"""    # check if drawed distance (if it exists) is
            return                 # not to small in neither x nor y-direction
        self.onselect(self.eventpress, self.eventrelease)
                                              # call desired function
        self.eventpress = None                # reset the variables to their
        self.eventrelease = None              #   inital values
        return False

    def update(self):
        'draw using newfangled blit or oldfangled draw depending on useblit'
        if self.useblit:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            self.ax.draw_artist(self.to_draw)
            self.canvas.blit(self.ax.bbox)
        else:
            self.canvas.draw_idle()
        return False

    def onmove(self, event):
        'on motion notify event if box/line is wanted'
        if self.eventpress is None or self.ignore(event): return
        x,y = event.xdata, event.ydata            # actual position (with
                                                  #   (button still pressed)
        if self.drawtype == 'box':
            minx, maxx = self.eventpress.xdata, x # click-x and actual mouse-x
            miny, maxy = self.eventpress.ydata, y # click-y and actual mouse-y
            if minx>maxx: minx, maxx = maxx, minx # get them in the right order
            if miny>maxy: miny, maxy = maxy, miny
            self.to_draw.set_x(minx)             # set lower left of box
            self.to_draw.set_y(miny)
            self.to_draw.set_width(maxx-minx)     # set width and height of box
            self.to_draw.set_height(maxy-miny)
            self.update()
            return False
        if self.drawtype == 'line':
            self.to_draw.set_data([self.eventpress.xdata, x],
                                  [self.eventpress.ydata, y])
            self.update()
            return False

    def set_active(self, active):
        """ Use this to activate / deactivate the RectangleSelector

            from your program with an boolean variable 'active'.
        """
        self.active = active

    def get_active(self):
        """ to get status of active mode (boolean variable)"""
        return self.active

class Lasso(Widget):
    def __init__(self, ax, xy, callback=None, useblit=True):
        self.axes = ax
        self.figure = ax.figure
        self.canvas = self.figure.canvas
        self.useblit = useblit
        if useblit:
            self.background = self.canvas.copy_from_bbox(self.axes.bbox)

        x, y = xy
        self.verts = [(x,y)]
        self.line = Line2D([x], [y], linestyle='-', color='black', lw=2)
        self.axes.add_line(self.line)
        self.callback = callback
        self.cids = []
        self.cids.append(self.canvas.mpl_connect('button_release_event', self.onrelease))
        self.cids.append(self.canvas.mpl_connect('motion_notify_event', self.onmove))

    def onrelease(self, event):
        if self.verts is not None:
            self.verts.append((event.xdata, event.ydata))
            if len(self.verts)>2:
                self.callback(self.verts)
            self.axes.lines.remove(self.line)
        self.verts = None
        for cid in self.cids:
            self.canvas.mpl_disconnect(cid)

    def onmove(self, event):
        if self.verts is None: return
        if event.inaxes != self.axes: return
        if event.button!=1: return
        self.verts.append((event.xdata, event.ydata))

        self.line.set_data(zip(*self.verts))

        if self.useblit:
            self.canvas.restore_region(self.background)
            self.axes.draw_artist(self.line)
            self.canvas.blit(self.axes.bbox)
        else:
            self.canvas.draw_idle()


# pvtol-nested.py - inner/outer design for vectored thrust aircraft
# RMM, 5 Sep 09
#
# This file works through a fairly complicated control design and
# analysis, corresponding to the planar vertical takeoff and landing
# (PVTOL) aircraft in Astrom and Murray, Chapter 11.  It is intended
# to demonstrate the basic functionality of the python-control
# package.
#

from __future__ import print_function

import os
import matplotlib.pyplot as plt  # MATLAB plotting functions
from control.matlab import *    # MATLAB-like functions
import numpy as np

# System parameters
m = 4               # mass of aircraft
J = 0.0475          # inertia around pitch axis
r = 0.25            # distance to center of force
g = 9.8             # gravitational constant
c = 0.05            # damping factor (estimated)

# Transfer functions for dynamics
Pi = tf([r], [J, 0, 0])  # inner loop (roll)
Po = tf([1], [m, c, 0])  # outer loop (position)

#
# Inner loop control design
#
# This is the controller for the pitch dynamics.  Goal is to have
# fast response for the pitch dynamics so that we can use this as a
# control for the lateral dynamics
#

# Design a simple lead controller for the system
k, a, b = 200, 2, 50
Ci = k*tf([1, a], [1, b])  # lead compensator
Li = Pi*Ci

# Bode plot for the open loop process
plt.figure(1)
bode(Pi)

# Bode plot for the loop transfer function, with margins
plt.figure(2)
bode(Li)

# Compute out the gain and phase margins
#! Not implemented
# gm, pm, wcg, wcp = margin(Li)

# Compute the sensitivity and complementary sensitivity functions
Si = feedback(1, Li)
Ti = Li*Si

# Check to make sure that the specification is met
plt.figure(3)
gangof4(Pi, Ci)

# Compute out the actual transfer function from u1 to v1 (see L8.2 notes)
# Hi = Ci*(1-m*g*Pi)/(1+Ci*Pi)
Hi = parallel(feedback(Ci, Pi), -m*g*feedback(Ci*Pi, 1))

plt.figure(4)
plt.clf()
plt.subplot(221)
bode(Hi)

# Now design the lateral control system
a, b, K = 0.02, 5, 2
Co = -K*tf([1, 0.3], [1, 10])  # another lead compensator
Lo = -m*g*Po*Co

plt.figure(5)
bode(Lo)  # margin(Lo)

# Finally compute the real outer-loop loop gain + responses
L = Co*Hi*Po
S = feedback(1, L)
T = feedback(L, 1)

# Compute stability margins
gm, pm, wgc, wpc = margin(L)
print("Gain margin: %g at %g" % (gm, wgc))
print("Phase margin: %g at %g" % (pm, wpc))

plt.figure(6)
plt.clf()
bode(L, np.logspace(-4, 3))

# Add crossover line to the magnitude plot
#
# Note: in matplotlib before v2.1, the following code worked:
#
#   plt.subplot(211); hold(True);
#   loglog([1e-4, 1e3], [1, 1], 'k-')
#
# In later versions of matplotlib the call to plt.subplot will clear the
# axes and so we have to extract the axes that we want to use by hand.
# In addition, hold() is deprecated so we no longer require it.
#
for ax in plt.gcf().axes:
    if ax.get_label() == 'control-bode-magnitude':
        break
ax.semilogx([1e-4, 1e3], 20*np.log10([1, 1]), 'k-')

#
# Replot phase starting at -90 degrees
#
# Get the phase plot axes
for ax in plt.gcf().axes:
    if ax.get_label() == 'control-bode-phase':
        break

# Recreate the frequency response and shift the phase
mag, phase, w = freqresp(L, np.logspace(-4, 3))
phase = phase - 360

# Replot the phase by hand
ax.semilogx([1e-4, 1e3], [-180, -180], 'k-')
ax.semilogx(w, np.squeeze(phase), 'b-')
ax.axis([1e-4, 1e3, -360, 0])
plt.xlabel('Frequency [deg]')
plt.ylabel('Phase [deg]')
# plt.set(gca, 'YTick', [-360, -270, -180, -90, 0])
# plt.set(gca, 'XTick', [10^-4, 10^-2, 1, 100])

#
# Nyquist plot for complete design
#
plt.figure(7)
plt.clf()
nyquist(L, (0.0001, 1000))

# Add a box in the region we are going to expand
plt.plot([-2, -2, 1, 1, -2], [-4, 4, 4, -4, -4], 'r-')

# Expanded region
plt.figure(8)
plt.clf()
nyquist(L)
plt.axis([-2, 1, -4, 4])

# set up the color
color = 'b'

# Add arrows to the plot
# H1 = L.evalfr(0.4); H2 = L.evalfr(0.41);
# arrow([real(H1), imag(H1)], [real(H2), imag(H2)], AM_normal_arrowsize, \
#  'EdgeColor', color, 'FaceColor', color);

# H1 = freqresp(L, 0.35); H2 = freqresp(L, 0.36);
# arrow([real(H2), -imag(H2)], [real(H1), -imag(H1)], AM_normal_arrowsize, \
#  'EdgeColor', color, 'FaceColor', color);

plt.figure(9)
Yvec, Tvec = step(T, np.linspace(0, 20))
plt.plot(Tvec.T, Yvec.T)

Yvec, Tvec = step(Co*S, np.linspace(0, 20))
plt.plot(Tvec.T, Yvec.T)

plt.figure(10)
plt.clf()
P, Z = pzmap(T, plot=True, grid=True)
print("Closed loop poles and zeros: ", P, Z)

# Gang of Four
plt.figure(11)
plt.clf()
gangof4(Hi*Po, Co)

if 'PYCONTROL_TEST_EXAMPLES' not in os.environ:
    plt.show()


#!/usr/bin/env python
"""
Plot mesh connectivities, facet orientations, global and local DOF ids etc.

To switch off plotting some mesh entities, set the corresponding color to
`None`.
"""
from __future__ import absolute_import
import sys
sys.path.append('.')
from argparse import ArgumentParser

import matplotlib.pyplot as plt

from sfepy.base.base import output
from sfepy.base.conf import dict_from_string
from sfepy.discrete.fem import Mesh, FEDomain
import sfepy.postprocess.plot_cmesh as pc

helps = {
    'vertex_opts' : 'plotting options for mesh vertices'
    ' [default: %(default)s]',
    'edge_opts' : 'plotting options for mesh edges'
    ' [default: %(default)s]',
    'face_opts' : 'plotting options for mesh faces'
    ' [default: %(default)s]',
    'cell_opts' : 'plotting options for mesh cells'
    ' [default: %(default)s]',
    'wireframe_opts' : 'plotting options for mesh wireframe'
    ' [default: %(default)s]',
    'no_axes' :
    'do not show the figure axes',
    'no_show' :
    'do not show the mesh plot figure',
}

def main():
    default_vertex_opts = """color='k', label_global=12,
                             label_local=8"""
    default_edge_opts = """color='b', label_global=12,
                           label_local=8"""
    default_face_opts = """color='g', label_global=12,
                           label_local=8"""
    default_cell_opts = """color='r', label_global=12"""
    default_wireframe_opts = "color='k'"

    parser = ArgumentParser(description=__doc__)
    parser.add_argument('--version', action='version', version='%(prog)s')
    parser.add_argument('--vertex-opts', metavar='dict-like',
                        action='store', dest='vertex_opts',
                        default=default_vertex_opts,
                        help=helps['vertex_opts'])
    parser.add_argument('--edge-opts', metavar='dict-like',
                        action='store', dest='edge_opts',
                        default=default_edge_opts,
                        help=helps['edge_opts'])
    parser.add_argument('--face-opts', metavar='dict-like',
                        action='store', dest='face_opts',
                        default=default_face_opts,
                        help=helps['face_opts'])
    parser.add_argument('--cell-opts', metavar='dict-like',
                        action='store', dest='cell_opts',
                        default=default_cell_opts,
                        help=helps['cell_opts'])
    parser.add_argument('--wireframe-opts', metavar='dict-like',
                        action='store', dest='wireframe_opts',
                        default=default_wireframe_opts,
                        help=helps['wireframe_opts'])
    parser.add_argument('--no-axes',
                        action='store_false', dest='axes',
                        help=helps['no_axes'])
    parser.add_argument('-n', '--no-show',
                        action='store_false', dest='show',
                        help=helps['no_show'])
    parser.add_argument('filename')
    parser.add_argument('figname', nargs='?')
    options = parser.parse_args()

    entities_opts = [
        dict_from_string(options.vertex_opts),
        dict_from_string(options.edge_opts),
        dict_from_string(options.face_opts),
        dict_from_string(options.cell_opts),
    ]
    wireframe_opts = dict_from_string(options.wireframe_opts)

    filename = options.filename

    mesh = Mesh.from_file(filename)
    output('Mesh:')
    output('  dimension: %d, vertices: %d, elements: %d'
           % (mesh.dim, mesh.n_nod, mesh.n_el))

    domain = FEDomain('domain', mesh)
    output(domain.cmesh)
    domain.cmesh.cprint(1)
    dim = domain.cmesh.dim

    if dim == 2: entities_opts.pop(2)

    ax = pc.plot_cmesh(None, domain.cmesh,
                       wireframe_opts=wireframe_opts,
                       entities_opts=entities_opts)
    ax.axis('image')
    if not options.axes:
        ax.axis('off')
    plt.tight_layout()

    if options.figname:
        fig = ax.figure
        fig.savefig(options.figname, bbox_inches='tight')

    if options.show:
        plt.show()

if __name__ == '__main__':
    main()


import pylab as pl
import numpy as np
import aplpy
import os
import copy
from astropy import log
from paths import h2copath, figurepath
import paths
import matplotlib
from scipy import stats as ss
from astropy.io import fits
matplotlib.rc_file(paths.pcpath('pubfiguresrc'))

pl.ioff()
# Close these figures so we can remake them in the appropriate size
for fignum in (4,5,6,7):
    pl.close(fignum)

cmap = pl.cm.RdYlBu_r
figsize = (20,10)

small_recen = dict(x=0.3, y=-0.03,width=1.05,height=0.27)
big_recen = dict(x=0.55, y=-0.075,width=2.3,height=0.40)

sgrb2x = [000.6773, 0.6578, 0.6672]
sgrb2y = [-00.0290, -00.0418, -00.0364]

vmin=10
vmax = 200

dustcolumn = '/Users/adam/work/gc/gcmosaic_column_conv36.fits'

# most of these come from make_ratiotem_cubesims
toloop = zip((
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens3e4.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens3e4.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens1e4.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens1e4.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens1e4_abund1e-8.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens1e4_abund1e-8.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens1e4_abund1e-10.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens1e4_abund1e-10.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens1e5.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens1e5.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens1e4_masked.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens1e4_masked.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens3e4_masked.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens3e4_masked.fits',
              'H2CO_321220_to_303202{0}_bl_integ_temperature_dens1e5_masked.fits',
              'H2CO_321220_to_303202{0}_bl_integ_weighted_temperature_dens1e5_masked.fits',
              'TemperatureCube_DendrogramObjects{0}_leaves_integ.fits',
              'TemperatureCube_DendrogramObjects{0}_leaves_integ_weighted.fits',
              'TemperatureCube_DendrogramObjects{0}_integ.fits',
              'TemperatureCube_DendrogramObjects{0}_integ_weighted.fits'),
             ('dens3e4',       'dens3e4_weighted',
              'dens1e4',       'dens1e4_weighted',
              'dens1e4_abund1e-8',       'dens1e4_abund1e-8_weighted',
              'dens1e4_abund1e-10',       'dens1e4_abund1e-10_weighted',
              'dens1e5',       'dens1e5_weighted',
              'dens1e4_masked','dens1e4_weighted_masked',
              'dens3e4_masked','dens3e4_weighted_masked',
              'dens1e5_masked','dens1e5_weighted_masked',
              'dendro_leaf','dendro_leaf_weighted',
              'dendro','dendro_weighted'))

#for vmax,vmax_str in zip((100,200),("_vmax100","")):
for vmax,vmax_str in zip((200,),("",)):
    for ftemplate,outtype in toloop:

        for smooth in ("","_smooth",):#"_vsmooth"):
            log.info(ftemplate.format(smooth)+"   "+outtype)
            fig = pl.figure(4, figsize=figsize)
            fig.clf()
            F = aplpy.FITSFigure(h2copath+ftemplate.format(smooth),
                                 convention='calabretta',
                                 figure=fig)

            cm = copy.copy(cmap)
            cm.set_bad((0.5,)*3)
            F.show_colorscale(cmap=cm,vmin=vmin,vmax=vmax)
            F.set_tick_labels_format('d.dd','d.dd')
            F.recenter(**small_recen)
            peaksn = os.path.join(h2copath,'APEX_H2CO_303_202{0}_bl_mask_integ.fits'.format(smooth))
            #F.show_contour(peaksn, levels=[4,7,11,20,38], colors=[(0.25,0.25,0.25,0.5)]*5, #smooth=3,
            #               linewidths=[1.0]*5,
            #               zorder=10, convention='calabretta')
            #color = (0.25,)*3
            #F.show_contour(peaksn, levels=[4,7,11,20,38], colors=[color + (alpha,) for alpha in (0.9,0.6,0.3,0.1,0.0)], #smooth=3,
            #               filled=True,
            #               #linewidths=[1.0]*5,
            #               zorder=10, convention='calabretta')
            color = (0.5,)*3 # should be same as background #888
            F.show_contour(peaksn, levels=[-1,0]+np.logspace(0.20,2).tolist(),
                           colors=[(0.5,0.5,0.5,1)]*2 + [color + (alpha,) for alpha in np.exp(-(np.logspace(0.20,2)-1.7)**2/(2.5**2*2.))], #smooth=3,
                           filled=True,
                           #linewidths=[1.0]*5,
                           layer='mask',
                           zorder=10, convention='calabretta',
                           rasterized=True)
            F.add_colorbar()
            F.colorbar.set_axis_label_text('T (K)')
            F.colorbar.set_axis_label_font(size=18)
            F.colorbar.set_label_properties(size=16)
            F.show_markers(sgrb2x, sgrb2y, color='k', facecolor='k', s=250,
                           edgecolor='k', alpha=0.9)
            F.save(os.path.join(figurepath, "big_maps", 'lores{0}{1}{2}_tmap_withmask.pdf'.format(smooth, outtype, vmax_str)))
            F.recenter(**big_recen)
            F.save(os.path.join(figurepath, "big_maps", 'big_lores{0}{1}{2}_tmap_withmask.pdf'.format(smooth, outtype, vmax_str)))
            log.info(os.path.join(figurepath, "big_maps", 'big_lores{0}{1}{2}_tmap_withmask.pdf'.format(smooth, outtype, vmax_str)))

            F.show_contour(dustcolumn,
                           levels=[5], colors=[(0,0,0,0.5)], zorder=15,
                           alpha=0.5,
                           linewidths=[0.5],
                           layer='dustcontour')
            F.recenter(**small_recen)
            F.save(os.path.join(figurepath, "big_maps", 'lores{0}{1}{2}_tmap_withcontours.pdf'.format(smooth, outtype, vmax_str)))
            F.recenter(**big_recen)
            F.save(os.path.join(figurepath, "big_maps", 'big_lores{0}{1}{2}_tmap_withcontours.pdf'.format(smooth, outtype, vmax_str)))
            log.info(os.path.join(figurepath, "big_maps", 'big_lores{0}{1}{2}_tmap_withcontours.pdf'.format(smooth, outtype, vmax_str)))

            F.hide_layer('mask')
            F.recenter(**small_recen)
            F.save(os.path.join(figurepath, "big_maps", 'lores{0}{1}{2}_tmap_nomask_withcontours.pdf'.format(smooth, outtype, vmax_str)))
            F.recenter(**big_recen)
            F.save(os.path.join(figurepath, "big_maps", 'big_lores{0}{1}{2}_tmap_nomask_withcontours.pdf'.format(smooth, outtype, vmax_str)))

            fig7 = pl.figure(7, figsize=figsize)
            fig7.clf()
            Fsn = aplpy.FITSFigure(peaksn, convention='calabretta', figure=fig7)
            Fsn.show_grayscale(vmin=0, vmax=10, stretch='linear', invert=True)
            Fsn.add_colorbar()
            Fsn.colorbar.set_axis_label_text('Peak S/N')
            Fsn.colorbar.set_axis_label_font(size=18)
            Fsn.colorbar.set_label_properties(size=16)
            Fsn.set_tick_labels_format('d.dd','d.dd')
            Fsn.recenter(**big_recen)
            Fsn.save(os.path.join(figurepath, "big_maps", 'big_lores{0}{1}{2}_peaksn.pdf'.format(smooth, outtype, vmax_str)))

            F.hide_layer('dustcontour')
            dusttemperature = '/Users/adam/work/gc/gcmosaic_temp_conv36.fits'
            F.show_contour(dusttemperature,
                           levels=[20,25],
                           colors=[(0,0,x,0.5) for x in [0.9,0.7,0.6,0.2]], zorder=20)
            F.recenter(**small_recen)
            F.save(os.path.join(figurepath, "big_maps",'lores{0}{1}{2}_tmap_withtdustcontours.pdf'.format(smooth, outtype, vmax_str)))
            F.recenter(**big_recen)
            F.save(os.path.join(figurepath, "big_maps",'big_lores{0}{1}{2}_tmap_withtdustcontours.pdf'.format(smooth, outtype, vmax_str)))
            log.info(os.path.join(figurepath, "big_maps",'big_lores{0}{1}{2}_tmap_withtdustcontours.pdf'.format(smooth, outtype, vmax_str)))

            im = fits.getdata(h2copath+ftemplate.format(smooth))
            data = im[np.isfinite(im)]

            fig9 = pl.figure(9)
            fig9.clf()
            ax9 = fig9.gca()
            h,l,p = ax9.hist(data, bins=np.linspace(0,300), alpha=0.5)
            shape, loc, scale = ss.lognorm.fit(data, floc=0)
            # from http://nbviewer.ipython.org/url/xweb.geos.ed.ac.uk/~jsteven5/blog/lognormal_distributions.ipynb
            mu = np.log(scale) # Mean of log(X) [but I want mean(x)]
            sigma = shape # Standard deviation of log(X)
            M = np.exp(mu) # Geometric mean == median
            s = np.exp(sigma) # Geometric standard deviation
            lnf = ss.lognorm(s=shape, loc=loc, scale=scale)
            pdf = lnf.pdf(np.arange(300))
            label1 = ("$\sigma_{{\mathrm{{ln}} x}} = {0:0.2f}$\n"
                      "$\mu_x = {1:0.2f}$\n"
                      "$\sigma_x = {2:0.2f}$".format(sigma, scale,s))
            pm = np.abs(ss.lognorm.interval(0.683, s=shape, loc=0, scale=scale) - scale)
            label2 = ("$x = {0:0.1f}^{{+{1:0.1f}}}_{{-{2:0.1f}}}$\n"
                      "$\sigma_{{\mathrm{{ln}} x}} = {3:0.1f}$\n"
                      .format(scale,
                              pm[1],
                              pm[0],
                              sigma,
                             ))
            ax9.plot(np.arange(300), pdf*h.max()/pdf.max(), linewidth=4, alpha=0.5,
                     label=label2)
            ax9.legend(loc='best')
            ax9.set_xlim(0,300)

            fig9.savefig(os.path.join(figurepath, "big_maps",
                                      'histogram_{0}{1}{2}_tmap.pdf'.format(smooth,
                                                                         outtype, vmax_str)),
                         bbox_inches='tight')

        #F.show_contour('h2co218222_all.fits', levels=[1,7,11,20,38], colors=['g']*5, smooth=1, zorder=5)
        #F.show_contour(datapath+'APEX_H2CO_merge_high_smooth_noise.fits', levels=[0.05,0.1], colors=['#0000FF']*2, zorder=3, convention='calabretta')
        #F.show_contour(datapath+'APEX_H2CO_merge_high_nhits.fits', levels=[9], colors=['#0000FF']*2, zorder=3, convention='calabretta',smooth=3)
        #F.show_regions('2014_expansion_targets_simpler.reg')
        #F.save('CMZ_H2CO_observed_planned.pdf')
        #F.show_rgb(background, wcs=wcs)
        #F.save('CMZ_H2CO_observed_planned_colorful.pdf')

fig = pl.figure(5, figsize=figsize)
fig.clf()
F2 = aplpy.FITSFigure(dusttemperature, convention='calabretta', figure=fig)
F2.show_colorscale(cmap=pl.cm.hot, vmin=10, vmax=40)
F2.add_colorbar()
F2.show_contour(h2copath+'H2CO_321220_to_303202_smooth_bl_integ_temperature.fits',
                convention='calabretta',
                levels=[30,75,100,150],
                cmap=pl.cm.BuGn)
F2.recenter(**small_recen)

F2.show_markers(sgrb2x, sgrb2y, color='k', facecolor='k', s=250,
               edgecolor='k', alpha=0.9)

F2.save(os.path.join(figurepath, "big_maps",'H2COtemperatureOnDust.pdf'))

F2.recenter(**big_recen)
F2.save(os.path.join(figurepath, "big_maps",'big_H2COtemperatureOnDust.pdf'))

for vmax in (100,200):
    fig = pl.figure(6, figsize=figsize)
    fig.clf()
    F = aplpy.FITSFigure('/Users/adam/work/gc/Tkin-GC.fits.gz',
                         convention='calabretta',
                         figure=fig)

    cm = copy.copy(cmap)
    cm.set_bad((0.5,)*3)
    F.show_colorscale(cmap=cm,vmin=vmin,vmax=vmax)
    F.set_tick_labels_format('d.dd','d.dd')
    F.recenter(**small_recen)
    F.add_colorbar()
    F.colorbar.set_axis_label_text('T (K)')
    F.colorbar.set_axis_label_font(size=18)
    F.colorbar.set_label_properties(size=16)
    F.show_markers(sgrb2x, sgrb2y, color='k', facecolor='k', s=250,
                   edgecolor='k', alpha=0.9)

    F.save(os.path.join(figurepath, "big_maps", 'ott2014_nh3_tmap_15to{0}.pdf'.format(vmax)))
    F.show_colorscale(cmap=cm,vmin=vmin,vmax=80)
    F.save(os.path.join(figurepath, "big_maps", 'ott2014_nh3_tmap_15to80.pdf'))

    F.show_contour(dustcolumn,
                   levels=[5], colors=[(0,0,0,0.5)], zorder=15,
                   alpha=0.5,
                   linewidths=[0.5],
                   layer='dustcontour')
    F.save(os.path.join(figurepath, "big_maps", 'ott2014_nh3_tmap_15to80_withcontours.pdf'))
    F.show_colorscale(cmap=cm,vmin=vmin,vmax=vmax)
    F.save(os.path.join(figurepath, "big_maps", 'ott2014_nh3_tmap_15to{0}_withcontours.pdf'.format(vmax)))


"""ABCs."""

# Authors: Guillaume Favelier <guillaume.favelier@gmail.com
#          Eric Larson <larson.eric.d@gmail.com>
#
# License: Simplified BSD

from abc import ABC, abstractmethod, abstractclassmethod
from contextlib import nullcontext
import warnings

from ..utils import tight_layout

class _AbstractRenderer(ABC):
    @abstractclassmethod
    def __init__(self, fig=None, size=(600, 600), bgcolor=(0., 0., 0.),
                 name=None, show=False, shape=(1, 1)):
        """Set up the scene."""
        pass

    @abstractclassmethod
    def subplot(self, x, y):
        """Set the active subplot."""
        pass

    @abstractclassmethod
    def scene(self):
        """Return scene handle."""
        pass

    @abstractclassmethod
    def set_interaction(self, interaction):
        """Set interaction mode."""
        pass

    @abstractclassmethod
    def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
             backface_culling=False, scalars=None, colormap=None,
             vmin=None, vmax=None, interpolate_before_map=True,
             representation='surface', line_width=1., normals=None,
             polygon_offset=None, **kwargs):
        """Add a mesh in the scene.

        Parameters
        ----------
        x : array, shape (n_vertices,)
           The array containing the X component of the vertices.
        y : array, shape (n_vertices,)
           The array containing the Y component of the vertices.
        z : array, shape (n_vertices,)
           The array containing the Z component of the vertices.
        triangles : array, shape (n_polygons, 3)
           The array containing the indices of the polygons.
        color : tuple | str
            The color of the mesh as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        opacity : float
            The opacity of the mesh.
        shading : bool
            If True, enable the mesh shading.
        backface_culling : bool
            If True, enable backface culling on the mesh.
        scalars : ndarray, shape (n_vertices,)
            The scalar valued associated to the vertices.
        vmin : float | None
            vmin is used to scale the colormap.
            If None, the min of the data will be used
        vmax : float | None
            vmax is used to scale the colormap.
            If None, the max of the data will be used
        colormap :
            The colormap to use.
        interpolate_before_map :
            Enabling makes for a smoother scalars display. Default is True.
            When False, OpenGL will interpolate the mapped colors which can
            result is showing colors that are not present in the color map.
        representation : str
            The representation of the mesh: either 'surface' or 'wireframe'.
        line_width : int
            The width of the lines when representation='wireframe'.
        normals : array, shape (n_vertices, 3)
            The array containing the normal of each vertex.
        polygon_offset : float
            If not None, the factor used to resolve coincident topology.
        kwargs : args
            The arguments to pass to triangular_mesh

        Returns
        -------
        surface :
            Handle of the mesh in the scene.
        """
        pass

    @abstractclassmethod
    def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,
                vmin=None, vmax=None, colormap=None,
                normalized_colormap=False, kind='line', color=None):
        """Add a contour in the scene.

        Parameters
        ----------
        surface : surface object
            The mesh to use as support for contour.
        scalars : ndarray, shape (n_vertices,)
            The scalar valued associated to the vertices.
        contours : int | list
             Specifying a list of values will only give the requested contours.
        width : float
            The width of the lines or radius of the tubes.
        opacity : float
            The opacity of the contour.
        vmin : float | None
            vmin is used to scale the colormap.
            If None, the min of the data will be used
        vmax : float | None
            vmax is used to scale the colormap.
            If None, the max of the data will be used
        colormap :
            The colormap to use.
        normalized_colormap : bool
            Specify if the values of the colormap are between 0 and 1.
        kind : 'line' | 'tube'
            The type of the primitives to use to display the contours.
        color :
            The color of the mesh as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        """
        pass

    @abstractclassmethod
    def surface(self, surface, color=None, opacity=1.0,
                vmin=None, vmax=None, colormap=None,
                normalized_colormap=False, scalars=None,
                backface_culling=False, polygon_offset=None):
        """Add a surface in the scene.

        Parameters
        ----------
        surface : surface object
            The information describing the surface.
        color : tuple | str
            The color of the surface as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        opacity : float
            The opacity of the surface.
        vmin : float | None
            vmin is used to scale the colormap.
            If None, the min of the data will be used
        vmax : float | None
            vmax is used to scale the colormap.
            If None, the max of the data will be used
        colormap :
            The colormap to use.
        scalars : ndarray, shape (n_vertices,)
            The scalar valued associated to the vertices.
        backface_culling : bool
            If True, enable backface culling on the surface.
        polygon_offset : float
            If not None, the factor used to resolve coincident topology.
        """
        pass

    @abstractclassmethod
    def sphere(self, center, color, scale, opacity=1.0,
               resolution=8, backface_culling=False,
               radius=None):
        """Add sphere in the scene.

        Parameters
        ----------
        center : ndarray, shape(n_center, 3)
            The list of centers to use for the sphere(s).
        color : tuple | str
            The color of the sphere as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        scale : float
            The scaling applied to the spheres. The given value specifies
            the maximum size in drawing units.
        opacity : float
            The opacity of the sphere(s).
        resolution : int
            The resolution of the sphere created. This is the number
            of divisions along theta and phi.
        backface_culling : bool
            If True, enable backface culling on the sphere(s).
        radius : float | None
            Replace the glyph scaling by a fixed radius value for each
            sphere (not supported by mayavi).
        """
        pass

    @abstractclassmethod
    def tube(self, origin, destination, radius=0.001, color='white',
             scalars=None, vmin=None, vmax=None, colormap='RdBu',
             normalized_colormap=False, reverse_lut=False):
        """Add tube in the scene.

        Parameters
        ----------
        origin : array, shape(n_lines, 3)
            The coordinates of the first end of the tube(s).
        destination : array, shape(n_lines, 3)
            The coordinates of the other end of the tube(s).
        radius : float
            The radius of the tube(s).
        color : tuple | str
            The color of the tube as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        scalars : array, shape (n_quivers,) | None
            The optional scalar data to use.
        vmin : float | None
            vmin is used to scale the colormap.
            If None, the min of the data will be used
        vmax : float | None
            vmax is used to scale the colormap.
            If None, the max of the data will be used
        colormap :
            The colormap to use.
        opacity : float
            The opacity of the tube(s).
        backface_culling : bool
            If True, enable backface culling on the tube(s).
        reverse_lut : bool
            If True, reverse the lookup table.

        Returns
        -------
        surface :
            Handle of the tube in the scene.
        """
        pass

    @abstractclassmethod
    def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8,
                 glyph_height=None, glyph_center=None, glyph_resolution=None,
                 opacity=1.0, scale_mode='none', scalars=None,
                 backface_culling=False, colormap=None, vmin=None, vmax=None,
                 line_width=2., name=None):
        """Add quiver3d in the scene.

        Parameters
        ----------
        x : array, shape (n_quivers,)
            The X component of the position of the quiver.
        y : array, shape (n_quivers,)
            The Y component of the position of the quiver.
        z : array, shape (n_quivers,)
            The Z component of the position of the quiver.
        u : array, shape (n_quivers,)
            The last X component of the quiver.
        v : array, shape (n_quivers,)
            The last Y component of the quiver.
        w : array, shape (n_quivers,)
            The last Z component of the quiver.
        color : tuple | str
            The color of the quiver as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        scale : float
            The scaling applied to the glyphs. The size of the glyph
            is by default calculated from the inter-glyph spacing.
            The given value specifies the maximum glyph size in drawing units.
        mode : 'arrow', 'cone' or 'cylinder'
            The type of the quiver.
        resolution : int
            The resolution of the glyph created. Depending on the type of
            glyph, it represents the number of divisions in its geometric
            representation.
        glyph_height : float
            The height of the glyph used with the quiver.
        glyph_center : tuple
            The center of the glyph used with the quiver: (x, y, z).
        glyph_resolution : float
            The resolution of the glyph used with the quiver.
        opacity : float
            The opacity of the quiver.
        scale_mode : 'vector', 'scalar' or 'none'
            The scaling mode for the glyph.
        scalars : array, shape (n_quivers,) | None
            The optional scalar data to use.
        backface_culling : bool
            If True, enable backface culling on the quiver.
        colormap :
            The colormap to use.
        vmin : float | None
            vmin is used to scale the colormap.
            If None, the min of the data will be used
        vmax : float | None
            vmax is used to scale the colormap.
            If None, the max of the data will be used
        line_width : float
            The width of the 2d arrows.
        """
        pass

    @abstractclassmethod
    def text2d(self, x_window, y_window, text, size=14, color='white'):
        """Add 2d text in the scene.

        Parameters
        ----------
        x : float
            The X component to use as position of the text in the
            window coordinates system (window_width, window_height).
        y : float
            The Y component to use as position of the text in the
            window coordinates system (window_width, window_height).
        text : str
            The content of the text.
        size : int
            The size of the font.
        color : tuple | str
            The color of the text as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        """
        pass

    @abstractclassmethod
    def text3d(self, x, y, z, text, width, color='white'):
        """Add 2d text in the scene.

        Parameters
        ----------
        x : float
            The X component to use as position of the text.
        y : float
            The Y component to use as position of the text.
        z : float
            The Z component to use as position of the text.
        text : str
            The content of the text.
        width : float
            The width of the text.
        color : tuple | str
            The color of the text as a tuple (red, green, blue) of float
            values between 0 and 1 or a valid color name (i.e. 'white'
            or 'w').
        """
        pass

    @abstractclassmethod
    def scalarbar(self, source, color="white", title=None, n_labels=4,
                  bgcolor=None):
        """Add a scalar bar in the scene.

        Parameters
        ----------
        source :
            The object of the scene used for the colormap.
        color :
            The color of the label text.
        title : str | None
            The title of the scalar bar.
        n_labels : int | None
            The number of labels to display on the scalar bar.
        bgcolor :
            The color of the background when there is transparency.
        """
        pass

    @abstractclassmethod
    def show(self):
        """Render the scene."""
        pass

    @abstractclassmethod
    def close(self):
        """Close the scene."""
        pass

    @abstractclassmethod
    def set_camera(self, azimuth=None, elevation=None, distance=None,
                   focalpoint=None, roll=None, reset_camera=True):
        """Configure the camera of the scene.

        Parameters
        ----------
        azimuth : float
            The azimuthal angle of the camera.
        elevation : float
            The zenith angle of the camera.
        distance : float
            The distance to the focal point.
        focalpoint : tuple
            The focal point of the camera: (x, y, z).
        roll : float
            The rotation of the camera along its axis.
        reset_camera : bool
           If True, reset the camera properties beforehand.
        """
        pass

    @abstractclassmethod
    def reset_camera(self):
        """Reset the camera properties."""
        pass

    @abstractclassmethod
    def screenshot(self, mode='rgb', filename=None):
        """Take a screenshot of the scene.

        Parameters
        ----------
        mode : str
            Either 'rgb' or 'rgba' for values to return.
            Default is 'rgb'.
        filename : str | None
            If not None, save the figure to the disk.
        """
        pass

    @abstractclassmethod
    def project(self, xyz, ch_names):
        """Convert 3d points to a 2d perspective.

        Parameters
        ----------
        xyz : array, shape(n_points, 3)
            The points to project.
        ch_names : array, shape(_n_points,)
            Names of the channels.
        """
        pass

    @abstractclassmethod
    def enable_depth_peeling(self):
        """Enable depth peeling."""
        pass

    @abstractclassmethod
    def remove_mesh(self, mesh_data):
        """Remove the given mesh from the scene.

        Parameters
        ----------
        mesh_data : tuple | Surface
            The mesh to remove.
        """
        pass

class _AbstractToolBar(ABC):
    @abstractmethod
    def _tool_bar_load_icons(self):
        pass

    @abstractmethod
    def _tool_bar_initialize(self, name="default", window=None):
        pass

    @abstractmethod
    def _tool_bar_add_button(self, name, desc, func, icon_name=None,
                             shortcut=None):
        pass

    @abstractmethod
    def _tool_bar_update_button_icon(self, name, icon_name):
        pass

    @abstractmethod
    def _tool_bar_add_text(self, name, value, placeholder):
        pass

    @abstractmethod
    def _tool_bar_add_spacer(self):
        pass

    @abstractmethod
    def _tool_bar_add_file_button(self, name, desc, func, shortcut=None):
        pass

    @abstractmethod
    def _tool_bar_add_play_button(self, name, desc, func, shortcut=None):
        pass

    @abstractmethod
    def _tool_bar_set_theme(self, theme):
        pass

class _AbstractDock(ABC):
    @abstractmethod
    def _dock_initialize(self, window=None):
        pass

    @abstractmethod
    def _dock_finalize(self):
        pass

    @abstractmethod
    def _dock_show(self):
        pass

    @abstractmethod
    def _dock_hide(self):
        pass

    @abstractmethod
    def _dock_add_stretch(self, layout):
        pass

    @abstractmethod
    def _dock_add_layout(self, vertical=True):
        pass

    @abstractmethod
    def _dock_add_label(self, value, align=False, layout=None):
        pass

    @abstractmethod
    def _dock_add_button(self, name, callback, layout=None):
        pass

    @abstractmethod
    def _dock_named_layout(self, name, layout, compact):
        pass

    @abstractmethod
    def _dock_add_slider(self, name, value, rng, callback,
                         compact=True, double=False, layout=None):
        pass

    @abstractmethod
    def _dock_add_spin_box(self, name, value, rng, callback,
                           compact=True, double=True, layout=None):
        pass

    @abstractmethod
    def _dock_add_combo_box(self, name, value, rng,
                            callback, compact=True, layout=None):
        pass

    @abstractmethod
    def _dock_add_group_box(self, name, layout=None):
        pass

class _AbstractMenuBar(ABC):
    @abstractmethod
    def _menu_initialize(self, window=None):
        pass

    @abstractmethod
    def _menu_add_submenu(self, name, desc):
        pass

    @abstractmethod
    def _menu_add_button(self, menu_name, name, desc, func):
        pass

class _AbstractStatusBar(ABC):
    @abstractmethod
    def _status_bar_initialize(self, window=None):
        pass

    @abstractmethod
    def _status_bar_add_label(self, value, stretch=0):
        pass

    @abstractmethod
    def _status_bar_add_progress_bar(self, stretch=0):
        pass

    @abstractmethod
    def _status_bar_update(self):
        pass

class _AbstractPlayback(ABC):
    @abstractmethod
    def _playback_initialize(self, func, timeout, value, rng,
                             time_widget, play_widget):
        pass

class _AbstractLayout(ABC):
    @abstractmethod
    def _layout_initialize(self, max_width):
        pass

    @abstractmethod
    def _layout_add_widget(self, layout, widget, stretch=0):
        pass

class _AbstractWidget(ABC):
    def __init__(self, widget):
        self._widget = widget

    @property
    def widget(self):
        return self._widget

    @abstractmethod
    def set_value(self, value):
        pass

    @abstractmethod
    def get_value(self):
        pass

    @abstractmethod
    def set_range(self, rng):
        pass

    @abstractmethod
    def show(self):
        pass

    @abstractmethod
    def hide(self):
        pass

    @abstractmethod
    def update(self, repaint=True):
        pass

class _AbstractMplInterface(ABC):
    @abstractmethod
    def _mpl_initialize():
        pass

class _AbstractMplCanvas(ABC):
    def __init__(self, width, height, dpi):
        """Initialize the MplCanvas."""
        from matplotlib import rc_context
        from matplotlib.figure import Figure
        # prefer constrained layout here but live with tight_layout otherwise
        context = nullcontext
        self._extra_events = ('resize',)
        try:
            context = rc_context({'figure.constrained_layout.use': True})
            self._extra_events = ()
        except KeyError:
            pass
        with context:
            self.fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = self.fig.add_subplot(111)
        self.axes.set(xlabel='Time (sec)', ylabel='Activation (AU)')
        self.manager = None

    def _connect(self):
        for event in ('button_press', 'motion_notify') + self._extra_events:
            self.canvas.mpl_connect(
                event + '_event', getattr(self, 'on_' + event))

    def plot(self, x, y, label, update=True, **kwargs):
        """Plot a curve."""
        line, = self.axes.plot(
            x, y, label=label, **kwargs)
        if update:
            self.update_plot()
        return line

    def plot_time_line(self, x, label, update=True, **kwargs):
        """Plot the vertical line."""
        line = self.axes.axvline(x, label=label, **kwargs)
        if update:
            self.update_plot()
        return line

    def update_plot(self):
        """Update the plot."""
        with warnings.catch_warnings(record=True):
            warnings.filterwarnings('ignore', 'constrained_layout')
            self.canvas.draw()

    def set_color(self, bg_color, fg_color):
        """Set the widget colors."""
        self.axes.set_facecolor(bg_color)
        self.axes.xaxis.label.set_color(fg_color)
        self.axes.yaxis.label.set_color(fg_color)
        self.axes.spines['top'].set_color(fg_color)
        self.axes.spines['bottom'].set_color(fg_color)
        self.axes.spines['left'].set_color(fg_color)
        self.axes.spines['right'].set_color(fg_color)
        self.axes.tick_params(axis='x', colors=fg_color)
        self.axes.tick_params(axis='y', colors=fg_color)
        self.fig.patch.set_facecolor(bg_color)

    def show(self):
        """Show the canvas."""
        if self.manager is None:
            self.canvas.show()
        else:
            self.manager.show()

    def close(self):
        """Close the canvas."""
        self.canvas.close()

    def clear(self):
        """Clear internal variables."""
        self.close()
        self.axes.clear()
        self.fig.clear()
        self.canvas = None
        self.manager = None

    def on_resize(self, event):
        """Handle resize events."""
        tight_layout(fig=self.axes.figure)

class _AbstractBrainMplCanvas(_AbstractMplCanvas):
    def __init__(self, brain, width, height, dpi):
        """Initialize the MplCanvas."""
        super().__init__(width, height, dpi)
        self.brain = brain
        self.time_func = brain.callbacks["time"]

    def update_plot(self):
        """Update the plot."""
        leg = self.axes.legend(
            prop={'family': 'monospace', 'size': 'small'},
            framealpha=0.5, handlelength=1.,
            facecolor=self.brain._bg_color)
        for text in leg.get_texts():
            text.set_color(self.brain._fg_color)
        super().update_plot()

    def on_button_press(self, event):
        """Handle button presses."""
        # left click (and maybe drag) in progress in axes
        if (event.inaxes != self.axes or
                event.button != 1):
            return
        self.time_func(
            event.xdata, update_widget=True, time_as_index=False)

    on_motion_notify = on_button_press  # for now they can be the same

    def clear(self):
        """Clear internal variables."""
        super().clear()
        self.brain = None

class _AbstractWindow(ABC):
    def _window_initialize(self):
        self._window = None
        self._interactor = None
        self._mplcanvas = None
        self._show_traces = None
        self._separate_canvas = None
        self._interactor_fraction = None

    @abstractmethod
    def _window_close_connect(self, func):
        pass

    @abstractmethod
    def _window_get_dpi(self):
        pass

    @abstractmethod
    def _window_get_size(self):
        pass

    def _window_get_mplcanvas_size(self, fraction):
        ratio = (1 - fraction) / fraction
        dpi = self._window_get_dpi()
        w, h = self._window_get_size()
        h /= ratio
        return (w / dpi, h / dpi)

    @abstractmethod
    def _window_get_simple_canvas(self, width, height, dpi):
        pass

    @abstractmethod
    def _window_get_mplcanvas(self, brain, interactor_fraction, show_traces,
                              separate_canvas):
        pass

    @abstractmethod
    def _window_adjust_mplcanvas_layout(self):
        pass

    @abstractmethod
    def _window_get_cursor(self):
        pass

    @abstractmethod
    def _window_set_cursor(self, cursor):
        pass

    @abstractmethod
    def _window_new_cursor(self, name):
        pass

    @abstractmethod
    def _window_ensure_minimum_sizes(self):
        pass

    @abstractmethod
    def _window_set_theme(self, theme):
        pass


# Duhhhh
import numpy as np
import matplotlib.pyplot as plt
import seaborn

plt.close('all')

# Define the parameters
r = 20  # the production rate
gamma = 1 / 30  # the degradation rate
k = 200 # in units of concentration
max_R = 1000  # maximum number of R1 and R2
R1 = np.linspace(0, max_R, 500)
R2 = np.linspace(0, max_R, 500)

# Compute the nullclines.
R1_null = (r / gamma) / (1 + (R2 / k)**2)
R2_null = (r / gamma) / (1 + (R1 / k)**2)

# Plot the nullclines.
plt.figure()
plt.plot(R1, R1_null, label='dR1/dt = 0')
plt.plot(R2_null, R2, label='dR2/dt = 0')
plt.xlabel('R1')
plt.ylabel('R2')
plt.legend()
plt.show()

# Generate the vector fields
R1_m, R2_m = np.meshgrid(R1[1::30], R2[1::30])

# Compute the derivatives
dR1_dt = -gamma * R1_m + r / (1 + (R2_m / k)**2)
dR2_dt = -gamma * R2_m + r / (1 + (R1_m / k)**2)

# Plot the vector fields!!
plt.quiver(R1_m, R2_m, dR1_dt, dR2_dt)
plt.show()

# Plot the orbit.
time = 200
R1 = 800
R2 = 400

# Loop through time and integrate.
for t in range(time):
    dR1 = -gamma * R1 + r / (1 + (R2 / k)**2)
    dR2 = -gamma * R2 + r / (1 + (R1 / k)**2)

    # Add this change to our current position
    R1 = R1 + dR1
    # This is the same operation as above..
    R2 += dR2

    plt.plot(R1, R2, 'ro')
    plt.show()
    plt.pause(0.05)


import numpy as np
from sklearn.utils.testing import (assert_array_almost_equal,
                                   assert_array_equal, assert_true, assert_raise_message)
from sklearn.datasets import load_linnerud
from sklearn.cross_decomposition import pls_
from nose.tools import assert_equal

def test_pls():
    d = load_linnerud()
    X = d.data
    Y = d.target
    # 1) Canonical (symmetric) PLS (PLS 2 blocks canonical mode A)
    # ===========================================================
    # Compare 2 algo.: nipals vs. svd
    # ------------------------------
    pls_bynipals = pls_.PLSCanonical(n_components=X.shape[1])
    pls_bynipals.fit(X, Y)
    pls_bysvd = pls_.PLSCanonical(algorithm="svd", n_components=X.shape[1])
    pls_bysvd.fit(X, Y)
    # check equalities of loading (up to the sign of the second column)
    assert_array_almost_equal(
        pls_bynipals.x_loadings_,
        np.multiply(pls_bysvd.x_loadings_, np.array([1, -1, 1])), decimal=5,
        err_msg="nipals and svd implementation lead to different x loadings")

    assert_array_almost_equal(
        pls_bynipals.y_loadings_,
        np.multiply(pls_bysvd.y_loadings_, np.array([1, -1, 1])), decimal=5,
        err_msg="nipals and svd implementation lead to different y loadings")

    # Check PLS properties (with n_components=X.shape[1])
    # ---------------------------------------------------
    plsca = pls_.PLSCanonical(n_components=X.shape[1])
    plsca.fit(X, Y)
    T = plsca.x_scores_
    P = plsca.x_loadings_
    Wx = plsca.x_weights_
    U = plsca.y_scores_
    Q = plsca.y_loadings_
    Wy = plsca.y_weights_

    def check_ortho(M, err_msg):
        K = np.dot(M.T, M)
        assert_array_almost_equal(K, np.diag(np.diag(K)), err_msg=err_msg)

    # Orthogonality of weights
    # ~~~~~~~~~~~~~~~~~~~~~~~~
    check_ortho(Wx, "x weights are not orthogonal")
    check_ortho(Wy, "y weights are not orthogonal")

    # Orthogonality of latent scores
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    check_ortho(T, "x scores are not orthogonal")
    check_ortho(U, "y scores are not orthogonal")

    # Check X = TP' and Y = UQ' (with (p == q) components)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # center scale X, Y
    Xc, Yc, x_mean, y_mean, x_std, y_std =\
        pls_._center_scale_xy(X.copy(), Y.copy(), scale=True)
    assert_array_almost_equal(Xc, np.dot(T, P.T), err_msg="X != TP'")
    assert_array_almost_equal(Yc, np.dot(U, Q.T), err_msg="Y != UQ'")

    # Check that rotations on training data lead to scores
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Xr = plsca.transform(X)
    assert_array_almost_equal(Xr, plsca.x_scores_,
                              err_msg="rotation on X failed")
    Xr, Yr = plsca.transform(X, Y)
    assert_array_almost_equal(Xr, plsca.x_scores_,
                              err_msg="rotation on X failed")
    assert_array_almost_equal(Yr, plsca.y_scores_,
                              err_msg="rotation on Y failed")

    # "Non regression test" on canonical PLS
    # --------------------------------------
    # The results were checked against the R-package plspm
    pls_ca = pls_.PLSCanonical(n_components=X.shape[1])
    pls_ca.fit(X, Y)

    x_weights = np.array(
        [[-0.61330704,  0.25616119, -0.74715187],
         [-0.74697144,  0.11930791,  0.65406368],
         [-0.25668686, -0.95924297, -0.11817271]])
    assert_array_almost_equal(pls_ca.x_weights_, x_weights)

    x_rotations = np.array(
        [[-0.61330704,  0.41591889, -0.62297525],
         [-0.74697144,  0.31388326,  0.77368233],
         [-0.25668686, -0.89237972, -0.24121788]])
    assert_array_almost_equal(pls_ca.x_rotations_, x_rotations)

    y_weights = np.array(
        [[+0.58989127,  0.7890047,   0.1717553],
         [+0.77134053, -0.61351791,  0.16920272],
         [-0.23887670, -0.03267062,  0.97050016]])
    assert_array_almost_equal(pls_ca.y_weights_, y_weights)

    y_rotations = np.array(
        [[+0.58989127,  0.7168115,  0.30665872],
         [+0.77134053, -0.70791757,  0.19786539],
         [-0.23887670, -0.00343595,  0.94162826]])
    assert_array_almost_equal(pls_ca.y_rotations_, y_rotations)

    # 2) Regression PLS (PLS2): "Non regression test"
    # ===============================================
    # The results were checked against the R-packages plspm, misOmics and pls
    pls_2 = pls_.PLSRegression(n_components=X.shape[1])
    pls_2.fit(X, Y)

    x_weights = np.array(
        [[-0.61330704, -0.00443647,  0.78983213],
         [-0.74697144, -0.32172099, -0.58183269],
         [-0.25668686,  0.94682413, -0.19399983]])
    assert_array_almost_equal(pls_2.x_weights_, x_weights)

    x_loadings = np.array(
        [[-0.61470416, -0.24574278,  0.78983213],
         [-0.65625755, -0.14396183, -0.58183269],
         [-0.51733059,  1.00609417, -0.19399983]])
    assert_array_almost_equal(pls_2.x_loadings_, x_loadings)

    y_weights = np.array(
        [[+0.32456184,  0.29892183,  0.20316322],
         [+0.42439636,  0.61970543,  0.19320542],
         [-0.13143144, -0.26348971, -0.17092916]])
    assert_array_almost_equal(pls_2.y_weights_, y_weights)

    y_loadings = np.array(
        [[+0.32456184,  0.29892183,  0.20316322],
         [+0.42439636,  0.61970543,  0.19320542],
         [-0.13143144, -0.26348971, -0.17092916]])
    assert_array_almost_equal(pls_2.y_loadings_, y_loadings)

    # 3) Another non-regression test of Canonical PLS on random dataset
    # =================================================================
    # The results were checked against the R-package plspm
    n = 500
    p_noise = 10
    q_noise = 5
    # 2 latents vars:
    np.random.seed(11)
    l1 = np.random.normal(size=n)
    l2 = np.random.normal(size=n)
    latents = np.array([l1, l1, l2, l2]).T
    X = latents + np.random.normal(size=4 * n).reshape((n, 4))
    Y = latents + np.random.normal(size=4 * n).reshape((n, 4))
    X = np.concatenate(
        (X, np.random.normal(size=p_noise * n).reshape(n, p_noise)), axis=1)
    Y = np.concatenate(
        (Y, np.random.normal(size=q_noise * n).reshape(n, q_noise)), axis=1)
    np.random.seed(None)
    pls_ca = pls_.PLSCanonical(n_components=3)
    pls_ca.fit(X, Y)

    x_weights = np.array(
        [[0.65803719,  0.19197924,  0.21769083],
         [0.7009113,  0.13303969, -0.15376699],
         [0.13528197, -0.68636408,  0.13856546],
         [0.16854574, -0.66788088, -0.12485304],
         [-0.03232333, -0.04189855,  0.40690153],
         [0.1148816, -0.09643158,  0.1613305],
         [0.04792138, -0.02384992,  0.17175319],
         [-0.06781, -0.01666137, -0.18556747],
         [-0.00266945, -0.00160224,  0.11893098],
         [-0.00849528, -0.07706095,  0.1570547],
         [-0.00949471, -0.02964127,  0.34657036],
         [-0.03572177,  0.0945091,  0.3414855],
         [0.05584937, -0.02028961, -0.57682568],
         [0.05744254, -0.01482333, -0.17431274]])
    assert_array_almost_equal(pls_ca.x_weights_, x_weights)

    x_loadings = np.array(
        [[0.65649254,  0.1847647,  0.15270699],
         [0.67554234,  0.15237508, -0.09182247],
         [0.19219925, -0.67750975,  0.08673128],
         [0.2133631, -0.67034809, -0.08835483],
         [-0.03178912, -0.06668336,  0.43395268],
         [0.15684588, -0.13350241,  0.20578984],
         [0.03337736, -0.03807306,  0.09871553],
         [-0.06199844,  0.01559854, -0.1881785],
         [0.00406146, -0.00587025,  0.16413253],
         [-0.00374239, -0.05848466,  0.19140336],
         [0.00139214, -0.01033161,  0.32239136],
         [-0.05292828,  0.0953533,  0.31916881],
         [0.04031924, -0.01961045, -0.65174036],
         [0.06172484, -0.06597366, -0.1244497]])
    assert_array_almost_equal(pls_ca.x_loadings_, x_loadings)

    y_weights = np.array(
        [[0.66101097,  0.18672553,  0.22826092],
         [0.69347861,  0.18463471, -0.23995597],
         [0.14462724, -0.66504085,  0.17082434],
         [0.22247955, -0.6932605, -0.09832993],
         [0.07035859,  0.00714283,  0.67810124],
         [0.07765351, -0.0105204, -0.44108074],
         [-0.00917056,  0.04322147,  0.10062478],
         [-0.01909512,  0.06182718,  0.28830475],
         [0.01756709,  0.04797666,  0.32225745]])
    assert_array_almost_equal(pls_ca.y_weights_, y_weights)

    y_loadings = np.array(
        [[0.68568625,  0.1674376,  0.0969508],
         [0.68782064,  0.20375837, -0.1164448],
         [0.11712173, -0.68046903,  0.12001505],
         [0.17860457, -0.6798319, -0.05089681],
         [0.06265739, -0.0277703,  0.74729584],
         [0.0914178,  0.00403751, -0.5135078],
         [-0.02196918, -0.01377169,  0.09564505],
         [-0.03288952,  0.09039729,  0.31858973],
         [0.04287624,  0.05254676,  0.27836841]])
    assert_array_almost_equal(pls_ca.y_loadings_, y_loadings)

    # Orthogonality of weights
    # ~~~~~~~~~~~~~~~~~~~~~~~~
    check_ortho(pls_ca.x_weights_, "x weights are not orthogonal")
    check_ortho(pls_ca.y_weights_, "y weights are not orthogonal")

    # Orthogonality of latent scores
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    check_ortho(pls_ca.x_scores_, "x scores are not orthogonal")
    check_ortho(pls_ca.y_scores_, "y scores are not orthogonal")

def test_PLSSVD():
    # Let's check the PLSSVD doesn't return all possible component but just
    # the specificied number
    d = load_linnerud()
    X = d.data
    Y = d.target
    n_components = 2
    for clf in [pls_.PLSSVD, pls_.PLSRegression, pls_.PLSCanonical]:
        pls = clf(n_components=n_components)
        pls.fit(X, Y)
        assert_equal(n_components, pls.y_scores_.shape[1])

def test_univariate_pls_regression():
    # Ensure 1d Y is correctly interpreted
    d = load_linnerud()
    X = d.data
    Y = d.target

    clf = pls_.PLSRegression()
    # Compare 1d to column vector
    model1 = clf.fit(X, Y[:, 0]).coef_
    model2 = clf.fit(X, Y[:, :1]).coef_
    assert_array_almost_equal(model1, model2)

def test_predict_transform_copy():
    # check that the "copy" keyword works
    d = load_linnerud()
    X = d.data
    Y = d.target
    clf = pls_.PLSCanonical()
    X_copy = X.copy()
    Y_copy = Y.copy()
    clf.fit(X, Y)
    # check that results are identical with copy
    assert_array_almost_equal(clf.predict(X), clf.predict(X.copy(), copy=False))
    assert_array_almost_equal(clf.transform(X), clf.transform(X.copy(), copy=False))

    # check also if passing Y
    assert_array_almost_equal(clf.transform(X, Y),
                              clf.transform(X.copy(), Y.copy(), copy=False))
    # check that copy doesn't destroy
    # we do want to check exact equality here
    assert_array_equal(X_copy, X)
    assert_array_equal(Y_copy, Y)
    # also check that mean wasn't zero before (to make sure we didn't touch it)
    assert_true(np.all(X.mean(axis=0) != 0))

def test_scale():
    d = load_linnerud()
    X = d.data
    Y = d.target

    # causes X[:, -1].std() to be zero
    X[:, -1] = 1.0

    for clf in [pls_.PLSCanonical(), pls_.PLSRegression(),
                pls_.PLSSVD()]:
        clf.set_params(scale=True)
        clf.fit(X, Y)

def test_pls_errors():
    d = load_linnerud()
    X = d.data
    Y = d.target
    for clf in [pls_.PLSCanonical(), pls_.PLSRegression(),
                pls_.PLSSVD()]:
        clf.n_components = 4
        assert_raise_message(ValueError, "Invalid number of components", clf.fit, X, Y)