"""
    Plot panel.
"""
from __future__ import print_function

import logging
import traceback
import math
import os
import operator
import copy

import wx
import numpy as np

# Try a normal import first
# If it fails, try specifying a version
import matplotlib
matplotlib.interactive(False)
#Use the WxAgg back end. The Wx one takes too long to render
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
from matplotlib.font_manager import FontProperties
import pylab
DEFAULT_CMAP = pylab.cm.jet

from sas.sasgui.guiframe.events import StatusEvent

#TODO: make the plottables interactive
from . import transform
from .TextDialog import TextDialog
from .LabelDialog import LabelDialog
from .binder import BindArtist
from .convert_units import convert_unit
from .plottables import Graph
from .toolbar import NavigationToolBar, PlotPrintout, bind

DEBUG = False
logger = logging.getLogger(__name__)

def show_tree(obj, d=0):
    """Handy function for displaying a tree of graph objects"""
    print("%s%s" % ("-"*d, obj.__class__.__name__))
    if 'get_children' in dir(obj):
        for a in obj.get_children(): show_tree(a, d + 1)



def _rescale(lo, hi, step, pt=None, bal=None, scale='linear'):
    """
        Rescale (lo,hi) by step, returning the new (lo,hi)
        The scaling is centered on pt, with positive values of step
        driving lo/hi away from pt and negative values pulling them in.
        If bal is given instead of point, it is already in [0,1] coordinates.

        This is a helper function for step-based zooming.

    """
    # Convert values into the correct scale for a linear transformation
    # TODO: use proper scale transformers
    loprev = lo
    hiprev = hi
    if scale == 'log':
        assert lo > 0
        if lo > 0:
            lo = math.log10(lo)
        if hi > 0:
            hi = math.log10(hi)
        if pt is not None:
            pt = math.log10(pt)

    # Compute delta from axis range * %, or 1-% if persent is negative
    if step > 0:
        delta = float(hi - lo) * step / 100
    else:
        delta = float(hi - lo) * step / (100 - step)

    # Add scale factor proportionally to the lo and hi values,
    # preserving the
    # point under the mouse
    if bal is None:
        bal = float(pt - lo) / (hi - lo)
    lo = lo - (bal * delta)
    hi = hi + (1 - bal) * delta

    # Convert transformed values back to the original scale
    if scale == 'log':
        if (lo <= -250) or (hi >= 250):
            lo = loprev
            hi = hiprev
        else:
            lo, hi = math.pow(10., lo), math.pow(10., hi)
    return (lo, hi)


class PlotPanel(wx.Panel):
    """
    The PlotPanel has a Figure and a Canvas. OnSize events simply set a
    flag, and the actually redrawing of the
    figure is triggered by an Idle event.
    """
    def __init__(self, parent, id=-1, xtransform=None,
                 ytransform=None, scale='log_{10}',
                 color=None, dpi=None, **kwargs):
        """
        """
        wx.Panel.__init__(self, parent, id=id, **kwargs)
        self.parent = parent
        if hasattr(parent, "parent"):
            self.parent = self.parent.parent
        self.dimension = 1
        self.gotLegend = 0  # to begin, legend is not picked.
        self.legend_pos_loc = None
        self.legend = None
        self.line_collections_list = []
        self.figure = Figure(None, dpi, linewidth=2.0)
        self.color = '#b3b3b3'
        from .canvas import FigureCanvas
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.SetColor(color)
        self._resizeflag = True
        self._SetSize()
        self.subplot = self.figure.add_subplot(111)
        self.figure.subplots_adjust(left=0.2, bottom=.2)
        self.yscale = 'linear'
        self.xscale = 'linear'
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.EXPAND)
        #add toolbar
        self.enable_toolbar = True
        self.toolbar = None
        self.add_toolbar()
        self.SetSizer(self.sizer)

        # Graph object to manage the plottables
        self.graph = Graph()

        #Boolean value to keep track of whether current legend is
        #visible or not
        self.legend_on = True
        self.grid_on = False
        #Location of legend, default is 0 or 'best'
        self.legendLoc = 0
        self.position = None
        self._loc_labels = self.get_loc_label()

        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)

        # Define some constants
        self.colorlist = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
        self.symbollist = ['o', 'x', '^', 'v', '<', '>', '+',
                           's', 'd', 'D', 'h', 'H', 'p', '-']

        #List of texts currently on the plot
        self.textList = []
        self.selectedText = None
        #User scale
        if xtransform is not None:
            self.xLabel = xtransform
        else:
            self.xLabel = "log10(x)"
        if ytransform is not None:
            self.yLabel = ytransform
        else:
            self.yLabel = "log10(y)"
        self.viewModel = "--"
        # keep track if the previous transformation of x
        # and y in Property dialog
        self.prevXtrans = "log10(x)"
        self.prevYtrans = "log10(y)"
        self.scroll_id = self.canvas.mpl_connect('scroll_event', self.onWheel)
        #taking care of dragging
        self.motion_id = self.canvas.mpl_connect('motion_notify_event',
                                                 self.onMouseMotion)
        self.press_id = self.canvas.mpl_connect('button_press_event',
                                                self.onLeftDown)
        self.pick_id = self.canvas.mpl_connect('pick_event', self.onPick)
        self.release_id = self.canvas.mpl_connect('button_release_event',
                                                  self.onLeftUp)

        wx.EVT_RIGHT_DOWN(self, self.onLeftDown)
        # to turn axis off whenn resizing the panel
        self.resizing = False

        self.leftdown = False
        self.leftup = False
        self.mousemotion = False
        self.axes = [self.subplot]
        ## Fit dialog
        self._fit_dialog = None
        # Interactor
        self.connect = BindArtist(self.subplot.figure)
        #self.selected_plottable = None

        # new data for the fit
        from sas.sasgui.guiframe.dataFitting import Data1D
        self.fit_result = Data1D(x=[], y=[], dy=None)
        self.fit_result.symbol = 13
        #self.fit_result = Data1D(x=[], y=[],dx=None, dy=None)
        self.fit_result.name = "Fit"
        # For fit Dialog initial display
        self.xmin = 0.0
        self.xmax = 0.0
        self.xminView = 0.0
        self.xmaxView = 0.0
        self._scale_xlo = None
        self._scale_xhi = None
        self._scale_ylo = None
        self._scale_yhi = None
        self.Avalue = None
        self.Bvalue = None
        self.ErrAvalue = None
        self.ErrBvalue = None
        self.Chivalue = None

        # for 2D scale
        if scale != 'linear':
            scale = 'log_{10}'
        self.scale = scale
        self.data = None
        self.qx_data = None
        self.qy_data = None
        self.xmin_2D = None
        self.xmax_2D = None
        self.ymin_2D = None
        self.ymax_2D = None
        ## store reference to the current plotted vmin and vmax of plotted image
        ##z range in linear scale
        self.zmin_2D = None
        self.zmax_2D = None

        #index array
        self.index_x = None
        self.index_y = None

        #number of bins
        self.x_bins = None
        self.y_bins = None

        ## default color map
        self.cmap = DEFAULT_CMAP

        # Dragging info
        self.begDrag = False
        self.xInit = None
        self.yInit = None
        self.xFinal = None
        self.yFinal = None

        #axes properties
        self.xaxis_font = None
        self.xaxis_label = None
        self.xaxis_unit = None
        self.xaxis_color = 'black'
        self.xaxis_tick = None
        self.yaxis_font = None
        self.yaxis_label = None
        self.yaxis_unit = None
        self.yaxis_color = 'black'
        self.yaxis_tick = None

        # check if zoomed.
        self.is_zoomed = False
        # Plottables
        self.plots = {}

        # Default locations
        self._default_save_location = os.getcwd()
        # let canvas know about axes
        self.canvas.set_panel(self)
        self.ly = None
        self.q_ctrl = None
        #Bind focus to change the border color
        self.canvas.Bind(wx.EVT_SET_FOCUS, self.on_set_focus)
        self.canvas.Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus)

    def _SetInitialSize(self,):
        """
        """
        pixels = self.parent.GetClientSize()
        self.canvas.SetSize(pixels)
        self.figure.set_size_inches(pixels[0] / self.figure.get_dpi(),
                                    pixels[1] / self.figure.get_dpi(), forward=True)

    def On_Paint(self, event):
        """
        """
        self.canvas.SetBackgroundColour(self.color)

    def on_set_focus(self, event):
        """
        Send to the parenet the current panel on focus
        """
        # light blue
        self.color = '#0099f7'
        self.figure.set_edgecolor(self.color)
        if self.parent and self.window_caption:
            self.parent.send_focus_to_datapanel(self.window_caption)
        self.draw()

    def on_kill_focus(self, event):
        """
        Reset the panel color
        """
        # light grey
        self.color = '#b3b3b3'
        self.figure.set_edgecolor(self.color)
        self.draw()

    def set_resizing(self, resizing=False):
        """
        Set the resizing (True/False)
        """
        pass  # Not implemented

    def schedule_full_draw(self, func='append'):
        """
        Put self in schedule to full redraw list
        """
        pass  # Not implemented

    def add_toolbar(self):
        """
        add toolbar
        """
        self.enable_toolbar = True
        self.toolbar = NavigationToolBar(parent=self, canvas=self.canvas)
        bind(self.toolbar, wx.EVT_TOOL, self.onResetGraph, id=self.toolbar._NTB2_RESET)
        bind(self.toolbar, wx.EVT_TOOL, self.onContextMenu, id=self.toolbar._NTB2_HOME)
        self.toolbar.Realize()
        ## The 'SetToolBar()' is not working on MAC: JHC
        #if IS_MAC:
        # Mac platform (OSX 10.3, MacPython) does not seem to cope with
        # having a toolbar in a sizer. This work-around gets the buttons
        # back, but at the expense of having the toolbar at the top
        #self.SetToolBar(self.toolbar)
        #else:
        # On Windows platform, default window size is incorrect, so set
        # toolbar width to figure width.
        tw, th = self.toolbar.GetSize()
        fw, fh = self.canvas.GetSize()
        # By adding toolbar in sizer, we are able to put it at the bottom
        # of the frame - so appearance is closer to GTK version.
        # As noted above, doesn't work for Mac.
        self.toolbar.SetSize(wx.Size(fw, th))
        self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)

        # update the axes menu on the toolbar
        self.toolbar.update()

    def onLeftDown(self, event):
        """
        left button down and ready to drag
        """
        # Check that the LEFT button was pressed
        if event.button == 1:
            self.leftdown = True
            ax = event.inaxes
            for text in self.textList:
                if text.contains(event)[0]: # If user has clicked on text
                    self.selectedText = text
                    return

            if ax is not None:
                self.xInit, self.yInit = event.xdata, event.ydata
                try:
                    pos_x = float(event.xdata)  # / size_x
                    pos_y = float(event.ydata)  # / size_y
                    pos_x = "%8.3g" % pos_x
                    pos_y = "%8.3g" % pos_y
                    self.position = str(pos_x), str(pos_y)
                    wx.PostEvent(self.parent, StatusEvent(status=self.position))
                except:
                    self.position = None

    def onLeftUp(self, event):
        """
        Dragging is done
        """
        # Check that the LEFT button was released
        if event.button == 1:
            self.leftdown = False
            self.mousemotion = False
            self.leftup = True
            self.selectedText = None

        #release the legend
        if self.gotLegend == 1:
            self.gotLegend = 0
            self.set_legend_alpha(1)

    def set_legend_alpha(self, alpha=1):
        """
        Set legend alpha
        """
        if self.legend is not None:
            self.legend.legendPatch.set_alpha(alpha)

    def onPick(self, event):
        """
        On pick legend
        """
        legend = self.legend
        if event.artist == legend:
            #gets the box of the legend.
            bbox = self.legend.get_window_extent()
            #get mouse coordinates at time of pick.
            self.mouse_x = event.mouseevent.x
            self.mouse_y = event.mouseevent.y
            #get legend coordinates at time of pick.
            self.legend_x = bbox.xmin
            self.legend_y = bbox.ymin
            #indicates we picked up the legend.
            self.gotLegend = 1
            self.set_legend_alpha(0.5)

    def _on_legend_motion(self, event):
        """
        On legend in motion
        """
        ax = event.inaxes
        if ax is None:
            return
        # Event occurred inside a plotting area
        lo_x, hi_x = ax.get_xlim()
        lo_y, hi_y = ax.get_ylim()
        # How much the mouse moved.
        x = mouse_diff_x = self.mouse_x - event.x
        y = mouse_diff_y = self.mouse_y - event.y
        # Put back inside
        if x < lo_x:
            x = lo_x
        if x > hi_x:
            x = hi_x
        if y < lo_y:
            y = lo_y
        if y > hi_y:
            y = hi_y
        # Move the legend from its previous location by that same amount
        loc_in_canvas = (self.legend_x - mouse_diff_x,
                         self.legend_y - mouse_diff_y)
        # Transform into legend coordinate system
        trans_axes = self.legend.parent.transAxes.inverted()
        loc_in_norm_axes = trans_axes.transform_point(loc_in_canvas)
        self.legend_pos_loc = tuple(loc_in_norm_axes)
        self.legend._loc = self.legend_pos_loc
        self.resizing = True
        self.canvas.set_resizing(self.resizing)
        self.canvas.draw()

    def onMouseMotion(self, event):
        """
        check if the left button is press and the mouse in moving.
        computer delta for x and y coordinates and then calls draghelper
        to perform the drag
        """
        self.cusor_line(event)
        if self.gotLegend == 1 and self.leftdown:
            self._on_legend_motion(event)
            return

        if self.leftdown and self.selectedText is not None:
            # User has clicked on text and is dragging
            ax = event.inaxes
            if ax is not None:
                # Only move text if mouse is within axes
                self.selectedText.set_position((event.xdata, event.ydata))
                self._dragHelper(0, 0)
            else:
                # User has dragged outside of axes
                self.selectedText = None
            return

        if self.enable_toolbar:
            #Disable dragging without the toolbar to allow zooming with toolbar
            return
        self.mousemotion = True
        if self.leftdown and self.mousemotion:
            ax = event.inaxes
            if ax is not None:  # the dragging is perform inside the figure
                self.xFinal, self.yFinal = event.xdata, event.ydata
                # Check whether this is the first point
                if self.xInit is None:
                    self.xInit = self.xFinal
                    self.yInit = self.yFinal

                xdelta = self.xFinal - self.xInit
                ydelta = self.yFinal - self.yInit

                if self.xscale == 'log':
                    xdelta = math.log10(self.xFinal) - math.log10(self.xInit)
                if self.yscale == 'log':
                    ydelta = math.log10(self.yFinal) - math.log10(self.yInit)
                self._dragHelper(xdelta, ydelta)
            else:  # no dragging is perform elsewhere
                self._dragHelper(0, 0)

    def cusor_line(self, event):
        """
        """
        pass

    def _offset_graph(self):
        """
        Zoom and offset the graph to the last known settings
        """
        for ax in self.axes:
            if self._scale_xhi is not None and self._scale_xlo is not None:
                ax.set_xlim(self._scale_xlo, self._scale_xhi)
            if self._scale_yhi is not None and self._scale_ylo is not None:
                ax.set_ylim(self._scale_ylo, self._scale_yhi)

    def _dragHelper(self, xdelta, ydelta):
        """
        dragging occurs here
        """
        # Event occurred inside a plotting area
        for ax in self.axes:
            lo, hi = ax.get_xlim()
            newlo, newhi = lo - xdelta, hi - xdelta
            if self.xscale == 'log':
                if lo > 0:
                    newlo = math.log10(lo) - xdelta
                if hi > 0:
                    newhi = math.log10(hi) - xdelta
            if self.xscale == 'log':
                self._scale_xlo = math.pow(10, newlo)
                self._scale_xhi = math.pow(10, newhi)
                ax.set_xlim(math.pow(10, newlo), math.pow(10, newhi))
            else:
                self._scale_xlo = newlo
                self._scale_xhi = newhi
                ax.set_xlim(newlo, newhi)

            lo, hi = ax.get_ylim()
            newlo, newhi = lo - ydelta, hi - ydelta
            if self.yscale == 'log':
                if lo > 0:
                    newlo = math.log10(lo) - ydelta
                if hi > 0:
                    newhi = math.log10(hi) - ydelta
            if  self.yscale == 'log':
                self._scale_ylo = math.pow(10, newlo)
                self._scale_yhi = math.pow(10, newhi)
                ax.set_ylim(math.pow(10, newlo), math.pow(10, newhi))
            else:
                self._scale_ylo = newlo
                self._scale_yhi = newhi
                ax.set_ylim(newlo, newhi)
        self.canvas.draw_idle()

    def resetFitView(self):
        """
        For fit Dialog initial display
        """
        self.xmin = 0.0
        self.xmax = 0.0
        self.xminView = 0.0
        self.xmaxView = 0.0
        self._scale_xlo = None
        self._scale_xhi = None
        self._scale_ylo = None
        self._scale_yhi = None
        self.Avalue = None
        self.Bvalue = None
        self.ErrAvalue = None
        self.ErrBvalue = None
        self.Chivalue = None

    def onWheel(self, event):
        """
        Process mouse wheel as zoom events

        :param event: Wheel event

        """
        ax = event.inaxes
        step = event.step

        if ax is not None:
            # Event occurred inside a plotting area
            lo, hi = ax.get_xlim()
            lo, hi = _rescale(lo, hi, step,
                              pt=event.xdata, scale=ax.get_xscale())
            if not self.xscale == 'log' or lo > 0:
                self._scale_xlo = lo
                self._scale_xhi = hi
                ax.set_xlim((lo, hi))

            lo, hi = ax.get_ylim()
            lo, hi = _rescale(lo, hi, step, pt=event.ydata,
                              scale=ax.get_yscale())
            if not self.yscale == 'log' or lo > 0:
                self._scale_ylo = lo
                self._scale_yhi = hi
                ax.set_ylim((lo, hi))
        else:
            # Check if zoom happens in the axes
            xdata, ydata = None, None
            x, y = event.x, event.y

            for ax in self.axes:
                insidex, _ = ax.xaxis.contains(event)
                if insidex:
                    xdata, _ = ax.transAxes.inverted().transform_point((x, y))
                insidey, _ = ax.yaxis.contains(event)
                if insidey:
                    _, ydata = ax.transAxes.inverted().transform_point((x, y))
            if xdata is not None:
                lo, hi = ax.get_xlim()
                lo, hi = _rescale(lo, hi, step,
                                  bal=xdata, scale=ax.get_xscale())
                if not self.xscale == 'log' or lo > 0:
                    self._scale_xlo = lo
                    self._scale_xhi = hi
                    ax.set_xlim((lo, hi))
            if ydata is not None:
                lo, hi = ax.get_ylim()
                lo, hi = _rescale(lo, hi, step, bal=ydata,
                                  scale=ax.get_yscale())
                if not self.yscale == 'log' or lo > 0:
                    self._scale_ylo = lo
                    self._scale_yhi = hi
                    ax.set_ylim((lo, hi))
        self.canvas.draw_idle()

    def returnTrans(self):
        """
        Return values and labels used by Fit Dialog
        """
        return (self.xLabel, self.yLabel, self.Avalue, self.Bvalue,
                self.ErrAvalue, self.ErrBvalue, self.Chivalue)

    def setTrans(self, xtrans, ytrans):
        """

        :param xtrans: set x transformation on Property dialog
        :param ytrans: set y transformation on Property dialog

        """
        self.prevXtrans = xtrans
        self.prevYtrans = ytrans

    def onFitting(self, event):
        """
        when clicking on linear Fit on context menu , display Fitting Dialog
        """
        plot_dict = {}
        menu = event.GetEventObject()
        event_id = event.GetId()
        self.set_selected_from_menu(menu, event_id)
        plotlist = self.graph.returnPlottable()
        if self.graph.selected_plottable is not None:
            for item in plotlist:
                if item.id == self.graph.selected_plottable:
                    plot_dict[item] = plotlist[item]
        else:
            plot_dict = plotlist
        from .fitDialog import LinearFit

        if len(plot_dict.keys()) > 0:
            first_item = list(plot_dict.keys())[0]
            dlg = LinearFit(parent=None, plottable=first_item,
                            push_data=self.onFitDisplay,
                            transform=self.returnTrans,
                            title='Linear Fit')

            if ((self.xmin != 0.0) and (self.xmax != 0.0)
                    and (self.xminView != 0.0) and (self.xmaxView != 0.0)):
                dlg.setFitRange(self.xminView, self.xmaxView,
                                self.xmin, self.xmax)
            else:
                xlim = self.subplot.get_xlim()
                ylim = self.subplot.get_ylim()
                dlg.setFitRange(xlim[0], xlim[1], ylim[0], ylim[1])
            # It would be nice for this to NOT be modal (i.e. Show).
            # Not sure about other ramifications - for example
            # if a second linear fit is started before the first is closed.
            # consider for future - being able to work on the plot while
            # seing the fit values would be very nice  -- PDB 7/10/16
            dlg.ShowModal()

    def set_selected_from_menu(self, menu, id):
        """
        Set selected_plottable from context menu selection

        :param menu: context menu item
        :param id: menu item id
        """
        if len(self.plots) < 1:
            return
        name = menu.GetHelpString(id)
        for plot in self.plots.values():
            if plot.name == name:
                self.graph.selected_plottable = plot.id
                break

    def linear_plottable_fit(self, plot):
        """
            when clicking on linear Fit on context menu, display Fitting Dialog

            :param plot: PlotPanel owning the graph

        """
        from .fitDialog import LinearFit
        if self._fit_dialog is not None:
            return
        self._fit_dialog = LinearFit(None, plot, self.onFitDisplay,
                                     self.returnTrans, -1, 'Linear Fit')
        # Set the zoom area
        if self._scale_xhi is not None and self._scale_xlo is not None:
            self._fit_dialog.set_fit_region(self._scale_xlo, self._scale_xhi)
        # Register the close event
        self._fit_dialog.register_close(self._linear_fit_close)
        # Show a non-model dialog
        self._fit_dialog.Show()

    def _linear_fit_close(self):
        """
        A fit dialog was closed
        """
        self._fit_dialog = None

    def _onProperties(self, event):
        """
        when clicking on Properties on context menu ,
        The Property dialog is displayed
        The user selects a transformation for x or y value and
        a new plot is displayed
        """
        if self._fit_dialog is not None:
            self._fit_dialog.Destroy()
            self._fit_dialog = None
        plot_list = self.graph.returnPlottable()
        if len(plot_list.keys()) > 0:
            first_item = plot_list.keys()[0]
            if first_item.x != []:
                from .PropertyDialog import Properties
                dial = Properties(self, -1, 'Properties')
                dial.setValues(self.prevXtrans, self.prevYtrans, self.viewModel)
                if dial.ShowModal() == wx.ID_OK:
                    self.xLabel, self.yLabel, self.viewModel = dial.getValues()
                    self._onEVT_FUNC_PROPERTY()
                dial.Destroy()

    def set_yscale(self, scale='linear'):
        """
        Set the scale on Y-axis

        :param scale: the scale of y-axis

        """
        self.subplot.set_yscale(scale, nonposy='clip')
        self.yscale = scale

    def get_yscale(self):
        """

        :return: Y-axis scale

        """
        return self.yscale

    def set_xscale(self, scale='linear'):
        """
        Set the scale on x-axis

        :param scale: the scale of x-axis

        """
        self.subplot.set_xscale(scale)
        self.xscale = scale

    def get_xscale(self):
        """

        :return: x-axis scale

        """
        return self.xscale

    def SetColor(self, rgbtuple):
        """
        Set figure and canvas colours to be the same

        """
        if not rgbtuple:
            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
        col = [c / 255.0 for c in rgbtuple]
        self.figure.set_facecolor(col)
        self.figure.set_edgecolor(self.color)
        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))

    def _onSize(self, event):
        """
        """
        self._resizeflag = True

    def _onIdle(self, evt):
        """
        """
        if self._resizeflag:
            self._resizeflag = False
            self._SetSize()
            self.draw()

    def _SetSize(self, pixels=None):
        """
        This method can be called to force the Plot to be a desired size,
         which defaults to the ClientSize of the panel

        """
        if not pixels:
            pixels = tuple(self.GetClientSize())
        self.canvas.SetSize(pixels)
        self.figure.set_size_inches(float(pixels[0]) / self.figure.get_dpi(),
                                    float(pixels[1]) / self.figure.get_dpi())

    def draw(self):
        """
        Where the actual drawing happens

        """
        self.figure.canvas.draw_idle()

    def legend_picker(self, legend, event):
        """
            Pick up the legend patch
        """
        return self.legend.legendPatch.contains(event)

    def get_loc_label(self):
        """
        Associates label to a specific legend location
        """
        _labels = {}
        i = 0
        _labels['best'] = i
        i += 1
        _labels['upper right'] = i
        i += 1
        _labels['upper left'] = i
        i += 1
        _labels['lower left'] = i
        i += 1
        _labels['lower right'] = i
        i += 1
        _labels['right'] = i
        i += 1
        _labels['center left'] = i
        i += 1
        _labels['center right'] = i
        i += 1
        _labels['lower center'] = i
        i += 1
        _labels['upper center'] = i
        i += 1
        _labels['center'] = i
        return _labels

    def onSaveImage(self, evt):
        """
        Implement save image
        """
        self.toolbar.save_figure(evt)

    def onContextMenu(self, event):
        """
        Default context menu for a plot panel

        """
        # Slicer plot popup menu
        wx_id = wx.NewId()
        slicerpop = wx.Menu()
        slicerpop.Append(wx_id, '&Save image', 'Save image as PNG')
        wx.EVT_MENU(self, wx_id, self.onSaveImage)

        wx_id = wx.NewId()
        slicerpop.Append(wx_id, '&Printer setup', 'Set image size')
        wx.EVT_MENU(self, wx_id, self.onPrinterSetup)

        wx_id = wx.NewId()
        slicerpop.Append(wx_id, '&Print image', 'Print image ')
        wx.EVT_MENU(self, wx_id, self.onPrint)

        wx_id = wx.NewId()
        slicerpop.Append(wx_id, '&Copy', 'Copy to the clipboard')
        wx.EVT_MENU(self, wx_id, self.OnCopyFigureMenu)

        wx_id = wx.NewId()
        slicerpop.AppendSeparator()
        slicerpop.Append(wx_id, '&Properties')
        wx.EVT_MENU(self, wx_id, self._onProperties)

        wx_id = wx.NewId()
        slicerpop.AppendSeparator()
        slicerpop.Append(wx_id, '&Linear Fit')
        wx.EVT_MENU(self, wx_id, self.onFitting)

        wx_id = wx.NewId()
        slicerpop.AppendSeparator()
        slicerpop.Append(wx_id, '&Toggle Legend On/Off', 'Toggle Legend On/Off')
        wx.EVT_MENU(self, wx_id, self.onLegend)

        loc_menu = wx.Menu()
        for label in self._loc_labels:
            wx_id = wx.NewId()
            loc_menu.Append(wx_id, str(label), str(label))
            wx.EVT_MENU(self, wx_id, self.onChangeLegendLoc)
        wx_id = wx.NewId()
        slicerpop.AppendMenu(wx_id, '&Modify Legend Location', loc_menu)

        wx_id = wx.NewId()
        slicerpop.Append(wx_id, '&Modify Y Axis Label')
        wx.EVT_MENU(self, wx_id, self._on_yaxis_label)
        wx_id = wx.NewId()
        slicerpop.Append(wx_id, '&Modify X Axis Label')
        wx.EVT_MENU(self, wx_id, self._on_xaxis_label)

        try:
            # mouse event
            pos_evt = event.GetPosition()
            pos = self.ScreenToClient(pos_evt)
        except:
            # toolbar event
            pos_x, pos_y = self.toolbar.GetPosition()
            pos = (pos_x, pos_y + 5)

        self.PopupMenu(slicerpop, pos)

    def onToolContextMenu(self, event):
        """
        ContextMenu from toolbar

        :param event: toolbar event
        """
        # reset postion
        self.position = None
        if self.graph.selected_plottable is not None:
            self.graph.selected_plottable = None

        self.onContextMenu(event)

# modified kieranrcampbell ILL june2012
    def onLegend(self, legOnOff):
        """
        Toggles whether legend is visible/not visible
        """
        self.legend_on = legOnOff
        if not self.legend_on:
            for ax in self.axes:
                self.remove_legend(ax)
        else:
            # sort them by labels
            handles, labels = self.subplot.get_legend_handles_labels()
            hl = sorted(zip(handles, labels),
                        key=operator.itemgetter(1))
            handles2, labels2 = zip(*hl)
            self.line_collections_list = handles2
            self.legend = self.subplot.legend(handles2, labels2,
                                              prop=FontProperties(size=10),
                                              loc=self.legendLoc)
            if self.legend is not None:
                self.legend.set_picker(self.legend_picker)
                self.legend.set_axes(self.subplot)
                self.legend.set_zorder(20)

        self.subplot.figure.canvas.draw_idle()

    def onChangeLegendLoc(self, event):
        """
        Changes legend loc based on user input
        """
        menu = event.GetEventObject()
        label = menu.GetLabel(event.GetId())
        self.ChangeLegendLoc(label)

    def ChangeLegendLoc(self, label):
        """
        Changes legend loc based on user input
        """
        self.legendLoc = label
        self.legend_pos_loc = None
        # sort them by labels
        handles, labels = self.subplot.get_legend_handles_labels()
        hl = sorted(zip(handles, labels),
                    key=operator.itemgetter(1))
        handles2, labels2 = zip(*hl)
        self.line_collections_list = handles2
        self.legend = self.subplot.legend(handles2, labels2,
                                          prop=FontProperties(size=10),
                                          loc=self.legendLoc)
        if self.legend is not None:
            self.legend.set_picker(self.legend_picker)
            self.legend.set_axes(self.subplot)
            self.legend.set_zorder(20)
        self.subplot.figure.canvas.draw_idle()

    def remove_legend(self, ax=None):
        """
        Remove legend for ax or the current axes.
        """
        if ax is None:
            ax = pylab.gca()
        ax.legend_ = None

    def _on_addtext(self, event):
        """
        Allows you to add text to the plot
        """
        pos_x = 0
        pos_y = 0
        if self.position is not None:
            pos_x, pos_y = self.position
        else:
            pos_x, pos_y = 0.01, 1.00

        textdial = TextDialog(None, -1, 'Add Custom Text')
        if textdial.ShowModal() == wx.ID_OK:
            try:
                FONT = FontProperties()
                label = textdial.getText()
                xpos = pos_x
                ypos = pos_y
                font = FONT.copy()
                font.set_size(textdial.getSize())
                font.set_family(textdial.getFamily())
                font.set_style(textdial.getStyle())
                font.set_weight(textdial.getWeight())
                colour = textdial.getColor()
                if len(label) > 0 and xpos > 0 and ypos > 0:
                    new_text = self.subplot.text(str(xpos), str(ypos), label,
                                                 fontproperties=font,
                                                 color=colour)
                    self.textList.append(new_text)
                    self.subplot.figure.canvas.draw_idle()
            except:
                if self.parent is not None:
                    msg = "Add Text: Error. Check your property values..."
                    wx.PostEvent(self.parent, StatusEvent(status=msg))
                else:
                    raise
        textdial.Destroy()
        #Have a pop up box come up for user to type in the
        #text that they want to add...then create text Plottable
        #based on this and plot it at user designated coordinates

    def onGridOnOff(self, gridon_off):
        """
        Allows ON/OFF Grid
        """
        self.grid_on = gridon_off

        self.subplot.figure.canvas.draw_idle()

    def _on_xaxis_label(self, event):
        """
        Allows you to add text to the plot
        """
        xaxis_label, xaxis_unit, xaxis_font, xaxis_color, is_ok, is_tick \
            = self._on_axis_label(axis='x')
        if not is_ok:
            return

        self.xaxis_label = xaxis_label
        self.xaxis_unit = xaxis_unit
        self.xaxis_font = xaxis_font
        self.xaxis_color = xaxis_color
        if is_tick:
            self.xaxis_tick = xaxis_font

        if self.data is not None:
            # 2D
            self.xaxis(self.xaxis_label, self.xaxis_unit,
                       self.xaxis_font, self.xaxis_color, self.xaxis_tick)
            self.subplot.figure.canvas.draw_idle()
        else:
            # 1D
            self._check_zoom_plot()

    def _check_zoom_plot(self):
        """
        Check the zoom range and plot (1D only)
        """
        xlo, xhi = self.subplot.get_xlim()
        ylo, yhi = self.subplot.get_ylim()
        ## Set the view scale for all plots
        self._onEVT_FUNC_PROPERTY(False)
        if self.is_zoomed:
            # Recover the x,y limits
            self.subplot.set_xlim((xlo, xhi))
            self.subplot.set_ylim((ylo, yhi))

    @property
    def is_zoomed(self):
        toolbar_zoomed = self.toolbar.GetToolEnabled(self.toolbar.wx_ids['Back'])
        return self._is_zoomed or toolbar_zoomed

    @is_zoomed.setter
    def is_zoomed(self, value):
        self._is_zoomed = value

    def _on_yaxis_label(self, event):
        """
        Allows you to add text to the plot
        """
        yaxis_label, yaxis_unit, yaxis_font, yaxis_color, is_ok, is_tick \
            = self._on_axis_label(axis='y')
        if not is_ok:
            return

        self.yaxis_label = yaxis_label
        self.yaxis_unit = yaxis_unit
        self.yaxis_font = yaxis_font
        self.yaxis_color = yaxis_color
        if is_tick:
            self.yaxis_tick = yaxis_font

        if self.data is not None:
            # 2D
            self.yaxis(self.yaxis_label, self.yaxis_unit,
                        self.yaxis_font, self.yaxis_color, self.yaxis_tick)
            self.subplot.figure.canvas.draw_idle()
        else:
            # 1D
            self._check_zoom_plot()

    def _on_axis_label(self, axis='x'):
        """
        Modify axes labels

        :param axis: x or y axis [string]
        """
        is_ok = True
        title = 'Modify %s axis label' % axis
        font = 'serif'
        colour = 'black'
        if axis == 'x':
            label = self.xaxis_label
            unit = self.xaxis_unit
        else:
            label = self.yaxis_label
            unit = self.yaxis_unit
        textdial = TextDialog(None, -1, title, label, unit)
        if textdial.ShowModal() == wx.ID_OK:
            try:
                FONT = FontProperties()
                font = FONT.copy()
                font.set_size(textdial.getSize())
                font.set_family(textdial.getFamily())
                font.set_style(textdial.getStyle())
                font.set_weight(textdial.getWeight())
                unit = textdial.getUnit()
                colour = textdial.getColor()
                is_tick = textdial.getTickLabel()
                label_temp = textdial.getText()
                if r"\\" in label_temp:
                    label = label_temp.replace(r"\\", r"??")
                    if self.parent is not None:
                        msg = r"Add Label error: Can not use double '\\' "
                        msg += "characters..."
                        wx.PostEvent(self.parent, StatusEvent(status=msg))
                else:
                    label = label_temp
            except:
                if self.parent is not None:
                    msg = "Add Label: Error. Check your property values..."
                    wx.PostEvent(self.parent, StatusEvent(status=msg))
                else:
                    pass
        else:
            is_ok = False
            is_tick = True
        textdial.Destroy()
        return label, unit, font, colour, is_ok, is_tick

    def _on_removetext(self, event):
        """
        Removes all text from the plot.
        Eventually, add option to remove specific text boxes
        """
        num_text = len(self.textList)
        if num_text < 1:
            if self.parent is not None:
                msg = "Remove Text: Nothing to remove.  "
                wx.PostEvent(self.parent, StatusEvent(status=msg))
            else:
                raise
            return
        txt = self.textList[num_text - 1]
        try:
            text_remove = txt.get_text()
            txt.remove()
            if self.parent is not None:
                msg = "Removed Text: '%s'. " % text_remove
                wx.PostEvent(self.parent, StatusEvent(status=msg))
        except:
            if self.parent is not None:
                msg = "Remove Text: Error occurred. "
                wx.PostEvent(self.parent, StatusEvent(status=msg))
            else:
                raise
        self.textList.remove(txt)

        self.subplot.figure.canvas.draw_idle()

    def properties(self, prop):
        """
        Set some properties of the graph.
        The set of properties is not yet determined.

        """
        # The particulars of how they are stored and manipulated (e.g., do
        # we want an inventory internally) is not settled.  I've used a
        # property dictionary for now.
        #
        # How these properties interact with a user defined style file is
        # even less clear.

        # Properties defined by plot

        # Ricardo:
        # A empty label "$$" will prevent the panel from displaying!
        if prop["xlabel"]:
            self.subplot.set_xlabel(r"$%s$"%prop["xlabel"])
        if prop["ylabel"]:
            self.subplot.set_ylabel(r"$%s$"%prop["ylabel"])
        self.subplot.set_title(prop["title"])


    def clear(self):
        """Reset the plot"""
        # TODO: Redraw is brutal.  Render to a backing store and swap in
        # TODO: rather than redrawing on the fly.
        self.subplot.clear()

    def render(self):
        """Commit the plot after all objects are drawn"""
        # TODO: this is when the backing store should be swapped in.
        if self.legend_on:
            ax = self.subplot
            ax.texts = self.textList
            try:
                handles, labels = ax.get_legend_handles_labels()
                # sort them by labels
                hl = sorted(zip(handles, labels),
                            key=operator.itemgetter(1))
                handles2, labels2 = zip(*hl)
                self.line_collections_list = handles2
                self.legend = ax.legend(handles2, labels2,
                                        prop=FontProperties(size=10),
                                        loc=self.legendLoc)
                if self.legend is not None:
                    self.legend.set_picker(self.legend_picker)
                    self.legend.set_axes(self.subplot)
                    self.legend.set_zorder(20)

            except:
                self.legend = ax.legend(prop=FontProperties(size=10),
                                        loc=self.legendLoc)

    def xaxis(self, label, units, font=None, color='black', t_font=None):
        """xaxis label and units.

        Axis labels know about units.

        We need to do this so that we can detect when axes are not
        commesurate.  Currently this is ignored other than for formatting
        purposes.

        """

        self.xcolor = color
        if units.count("{") > 0 and units.count("$") < 2:
            units = '$' + units + '$'
        if label.count("{") > 0 and label.count("$") < 2:
            label = '$' + label + '$'
        if units != "":
            label = label + " (" + units + ")"
        if font:
            self.subplot.set_xlabel(label, fontproperties=font, color=color)
            if t_font is not None:
                for tick in self.subplot.xaxis.get_major_ticks():
                    tick.label.set_fontproperties(t_font)
                for line in self.subplot.xaxis.get_ticklines():
                    size = t_font.get_size()
                    line.set_markersize(size / 3)
        else:
            self.subplot.set_xlabel(label, color=color)
        pass

    def yaxis(self, label, units, font=None, color='black', t_font=None):
        """yaxis label and units."""
        self.ycolor = color
        if units.count("{") > 0 and units.count("$") < 2:
            units = '$' + units + '$'
        if label.count("{") > 0 and label.count("$") < 2:
            label = '$' + label + '$'
        if units != "":
            label = label + " (" + units + ")"
        if font:
            self.subplot.set_ylabel(label, fontproperties=font, color=color)
            if t_font is not None:
                for tick_label in self.subplot.get_yticklabels():
                    tick_label.set_fontproperties(t_font)
                for line in self.subplot.yaxis.get_ticklines():
                    size = t_font.get_size()
                    line.set_markersize(size / 3)
        else:
            self.subplot.set_ylabel(label, color=color)
        pass

    def _connect_to_xlim(self, callback):
        """Bind the xlim change notification to the callback"""
        def process_xlim(axes):
            lo, hi = subplot.get_xlim()
            callback(lo, hi)
        self.subplot.callbacks.connect('xlim_changed', process_xlim)

    def interactive_points(self, x, y, dx=None, dy=None, name='', color=0,
                           symbol=0, markersize=5, zorder=1, id=None,
                           label=None, hide_error=False):
        """Draw markers with error bars"""
        self.subplot.set_yscale('linear')
        self.subplot.set_xscale('linear')
        if id is None:
            id = name
        from .plottable_interactor import PointInteractor
        p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
        if p.markersize is not None:
            markersize = p.markersize
        p.points(x, y, dx=dx, dy=dy, color=color, symbol=symbol, zorder=zorder,
                 markersize=markersize, label=label, hide_error=hide_error)

        self.subplot.set_yscale(self.yscale, nonposy='clip')
        self.subplot.set_xscale(self.xscale)

    def interactive_curve(self, x, y, dy=None, name='', color=0,
                          symbol=0, zorder=1, id=None, label=None):
        """Draw markers with error bars"""
        self.subplot.set_yscale('linear')
        self.subplot.set_xscale('linear')
        if id is None:
            id = name
        from .plottable_interactor import PointInteractor
        p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
        p.curve(x, y, dy=dy, color=color, symbol=symbol, zorder=zorder,
                label=label)

        self.subplot.set_yscale(self.yscale, nonposy='clip')
        self.subplot.set_xscale(self.xscale)

    def plottable_selected(self, id):
        """
        Called to register a plottable as selected
        """
        #TODO: check that it really is in the list of plottables
        self.graph.selected_plottable = id

    def points(self, x, y, dx=None, dy=None,
               color=0, symbol=0, marker_size=5, label=None,
               id=None, hide_error=False):
        """Draw markers with error bars"""

        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
        if dx is not None and type(dx) == type(()):
            dx = nx.vstack((x - dx[0], dx[1] - x)).transpose()
        if dy is not None and type(dy) == type(()):
            dy = nx.vstack((y - dy[0], dy[1] - y)).transpose()
        if dx is None and dy is None:
            self.subplot.plot(x, y, color=self._color(color),
                              marker=self._symbol(symbol),
                              markersize=marker_size,
                              linestyle='',
                              label=label)
        else:
            col = self._color(color)
            if hide_error:
                self.subplot.plot(x, y, color=col,
                                  marker=self._symbol(symbol),
                                  markersize=marker_size,
                                  linestyle='',
                                  label=label)
            else:
                self.subplot.errorbar(x, y, yerr=dy, xerr=None,
                                      ecolor=col, capsize=2, linestyle='',
                                      barsabove=False,
                                      mec=col, mfc=col,
                                      marker=self._symbol(symbol),
                                      markersize=marker_size,
                                      lolims=False, uplims=False,
                                      xlolims=False, xuplims=False, label=label)

        self.subplot.set_yscale(self.yscale, nonposy='clip')
        self.subplot.set_xscale(self.xscale)

    def _onToggleScale(self, event):
        """
        toggle axis and replot image

        """
        zmin_2D_temp = self.zmin_2D
        zmax_2D_temp = self.zmax_2D
        if self.scale == 'log_{10}':
            self.scale = 'linear'
            if self.zmin_2D is not None:
                zmin_2D_temp = math.pow(10, self.zmin_2D)
            if self.zmax_2D is not None:
                zmax_2D_temp = math.pow(10, self.zmax_2D)
        else:
            self.scale = 'log_{10}'
            if self.zmin_2D is not None:
                # min log value: no log(negative)
                if self.zmin_2D <= 0:
                    zmin_2D_temp = -32
                else:
                    zmin_2D_temp = math.log10(self.zmin_2D)
            if self.zmax_2D is not None:
                zmax_2D_temp = math.log10(self.zmax_2D)

        self.image(data=self.data, qx_data=self.qx_data,
                   qy_data=self.qy_data, xmin=self.xmin_2D,
                   xmax=self.xmax_2D,
                   ymin=self.ymin_2D, ymax=self.ymax_2D,
                   cmap=self.cmap, zmin=zmin_2D_temp,
                   zmax=zmax_2D_temp)

    def image(self, data, qx_data, qy_data, xmin, xmax, ymin, ymax,
              zmin, zmax, color=0, symbol=0, markersize=0,
              label='data2D', cmap=DEFAULT_CMAP):
        """
        Render the current data

        """
        # TODO: include mask info in plotter
        self.data = data
        self.qx_data = qx_data
        self.qy_data = qy_data
        self.xmin_2D = xmin
        self.xmax_2D = xmax
        self.ymin_2D = ymin
        self.ymax_2D = ymax
        self.zmin_2D = zmin
        self.zmax_2D = zmax
        c = self._color(color)
        # If we don't have any data, skip.
        if self.data is None:
            return
        if self.data.ndim == 1:
            output = self._build_matrix()
        else:
            output = copy.deepcopy(self.data)
        # rescale data if necessary
        if self.scale == 'log_{10}':
            with np.errstate(all='ignore'):
                output = np.log10(output)
            index = np.isfinite(output)
            if not index.all():
                cutoff = (np.min(output[index]) - np.log10(2)
                          if index.any() else 0.)
                output[~index] = cutoff
        # TODO: fix handling of zmin_2D/zmax_2D in _onToggleScale
        # For now, use default vmin/vmax from data
        #vmin, vmax = self.zmin_2D, self.zmax_2D
        vmin, vmax = None, None
        self.cmap = cmap
        if self.dimension != 3:
            #Re-adjust colorbar
            self.subplot.figure.subplots_adjust(left=0.2, right=.8, bottom=.2)
            im = self.subplot.imshow(output, interpolation='nearest',
                                     origin='lower',
                                     vmin=vmin, vmax=vmax,
                                     cmap=self.cmap,
                                     extent=(self.xmin_2D, self.xmax_2D,
                                             self.ymin_2D, self.ymax_2D))

            cbax = self.subplot.figure.add_axes([0.84, 0.2, 0.02, 0.7])
        else:
            # clear the previous 2D from memory
            # mpl is not clf, so we do
            self.subplot.figure.clear()

            self.subplot.figure.subplots_adjust(left=0.1, right=.8, bottom=.1)

            X = self.x_bins[0:-1]
            Y = self.y_bins[0:-1]
            X, Y = np.meshgrid(X, Y)

            try:
                # mpl >= 1.0.0
                ax = self.subplot.figure.gca(projection='3d')
                #ax.disable_mouse_rotation()
                cbax = self.subplot.figure.add_axes([0.84, 0.1, 0.02, 0.8])
                if len(X) > 60:
                    ax.disable_mouse_rotation()
            except:
                # mpl < 1.0.0
                try:
                    from mpl_toolkits.mplot3d import Axes3D
                except:
                    logger.error("PlotPanel could not import Axes3D")
                self.subplot.figure.clear()
                ax = Axes3D(self.subplot.figure)
                if len(X) > 60:
                    ax.cla()
                cbax = None
            self.subplot.figure.canvas.resizing = False
            im = ax.plot_surface(X, Y, output, rstride=1, cstride=1, cmap=cmap,
                                 linewidth=0, antialiased=False)
            self.subplot.set_axis_off()

        if cbax is None:
            ax.set_frame_on(False)
            cb = self.subplot.figure.colorbar(im, shrink=0.8, aspect=20)
        else:
            cb = self.subplot.figure.colorbar(im, cax=cbax)
        cb.update_bruteforce(im)
        cb.set_label('$' + self.scale + '$')
        if self.dimension != 3:
            self.figure.canvas.draw_idle()
        else:
            self.figure.canvas.draw()

    def _build_matrix(self):
        """
        Build a matrix for 2d plot from a vector
        Returns a matrix (image) with ~ square binning
        Requirement: need 1d array formats of
        self.data, self.qx_data, and self.qy_data
        where each one corresponds to z, x, or y axis values

        """
        # No qx or qy given in a vector format
        if (self.qx_data is None or self.qy_data is None
                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1):
            # do we need deepcopy here?
            return copy.deepcopy(self.data)

        # maximum # of loops to fillup_pixels
        # otherwise, loop could never stop depending on data
        max_loop = 1
        # get the x and y_bin arrays.
        self._get_bins()
        # set zero to None

        #Note: Can not use scipy.interpolate.Rbf:
        # 'cause too many data points (>10000)<=JHC.
        # 1d array to use for weighting the data point averaging
        #when they fall into a same bin.
        weights_data = np.ones([self.data.size])
        # get histogram of ones w/len(data); this will provide
        #the weights of data on each bins
        weights, xedges, yedges = np.histogram2d(x=self.qy_data,
                                                    y=self.qx_data,
                                                    bins=[self.y_bins, self.x_bins],
                                                    weights=weights_data)
        # get histogram of data, all points into a bin in a way of summing
        image, xedges, yedges = np.histogram2d(x=self.qy_data,
                                                  y=self.qx_data,
                                                  bins=[self.y_bins, self.x_bins],
                                                  weights=self.data)
        # Now, normalize the image by weights only for weights>1:
        # If weight == 1, there is only one data point in the bin so
        # that no normalization is required.
        image[weights > 1] = image[weights > 1] / weights[weights > 1]
        # Set image bins w/o a data point (weight==0) as None (was set to zero
        # by histogram2d.)
        image[weights == 0] = None

        # Fill empty bins with 8 nearest neighbors only when at least
        #one None point exists
        loop = 0

        # do while loop until all vacant bins are filled up up
        #to loop = max_loop
        while not(np.isfinite(image[weights == 0])).all():
            if loop >= max_loop:  # this protects never-ending loop
                break
            image = self._fillup_pixels(image=image, weights=weights)
            loop += 1

        return image

    def _get_bins(self):
        """
        get bins
        set x_bins and y_bins into self, 1d arrays of the index with
        ~ square binning
        Requirement: need 1d array formats of
        self.qx_data, and self.qy_data
        where each one corresponds to  x, or y axis values
        """
        # No qx or qy given in a vector format
        if (self.qx_data is None or self.qy_data is None
                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1):
            # do we need deepcopy here?
            return copy.deepcopy(self.data)

        # find max and min values of qx and qy
        xmax = self.qx_data.max()
        xmin = self.qx_data.min()
        ymax = self.qy_data.max()
        ymin = self.qy_data.min()

        # calculate the range of qx and qy: this way, it is a little
        # more independent
        x_size = xmax - xmin
        y_size = ymax - ymin

        # estimate the # of pixels on each axes
        npix_y = int(math.floor(math.sqrt(len(self.qy_data))))
        npix_x = int(math.floor(len(self.qy_data) / npix_y))

        # bin size: x- & y-directions
        xstep = x_size / (npix_x - 1)
        ystep = y_size / (npix_y - 1)

        # max and min taking account of the bin sizes
        xmax = xmax + xstep / 2.0
        xmin = xmin - xstep / 2.0
        ymax = ymax + ystep / 2.0
        ymin = ymin - ystep / 2.0

        # store x and y bin centers in q space
        x_bins = np.linspace(xmin, xmax, npix_x)
        y_bins = np.linspace(ymin, ymax, npix_y)

        #set x_bins and y_bins
        self.x_bins = x_bins
        self.y_bins = y_bins

    def _fillup_pixels(self, image=None, weights=None):
        """
        Fill z values of the empty cells of 2d image matrix
        with the average over up-to next nearest neighbor points

        :param image: (2d matrix with some zi = None)

        :return: image (2d array )

        :TODO: Find better way to do for-loop below

        """
        # No image matrix given
        if (image is None or np.ndim(image) != 2
                or np.isfinite(image).all() or weights is None):
            return image
        # Get bin size in y and x directions
        len_y = len(image)
        len_x = len(image[1])
        temp_image = np.zeros([len_y, len_x])
        weit = np.zeros([len_y, len_x])
        # do for-loop for all pixels
        for n_y in range(len(image)):
            for n_x in range(len(image[1])):
                # find only null pixels
                if weights[n_y][n_x] > 0 or np.isfinite(image[n_y][n_x]):
                    continue
                else:
                    # find 4 nearest neighbors
                    # check where or not it is at the corner
                    if n_y != 0 and np.isfinite(image[n_y - 1][n_x]):
                        temp_image[n_y][n_x] += image[n_y - 1][n_x]
                        weit[n_y][n_x] += 1
                    if n_x != 0 and np.isfinite(image[n_y][n_x - 1]):
                        temp_image[n_y][n_x] += image[n_y][n_x - 1]
                        weit[n_y][n_x] += 1
                    if n_y != len_y - 1 and np.isfinite(image[n_y + 1][n_x]):
                        temp_image[n_y][n_x] += image[n_y + 1][n_x]
                        weit[n_y][n_x] += 1
                    if n_x != len_x - 1 and np.isfinite(image[n_y][n_x + 1]):
                        temp_image[n_y][n_x] += image[n_y][n_x + 1]
                        weit[n_y][n_x] += 1
                    # go 4 next nearest neighbors when no non-zero
                    # neighbor exists
                    if (n_y != 0 and n_x != 0
                            and np.isfinite(image[n_y - 1][n_x - 1])):
                        temp_image[n_y][n_x] += image[n_y - 1][n_x - 1]
                        weit[n_y][n_x] += 1
                    if (n_y != len_y - 1 and n_x != 0
                            and np.isfinite(image[n_y + 1][n_x - 1])):
                        temp_image[n_y][n_x] += image[n_y + 1][n_x - 1]
                        weit[n_y][n_x] += 1
                    if (n_y != len_y and n_x != len_x - 1
                            and np.isfinite(image[n_y - 1][n_x + 1])):
                        temp_image[n_y][n_x] += image[n_y - 1][n_x + 1]
                        weit[n_y][n_x] += 1
                    if (n_y != len_y - 1 and n_x != len_x - 1
                            and np.isfinite(image[n_y + 1][n_x + 1])):
                        temp_image[n_y][n_x] += image[n_y + 1][n_x + 1]
                        weit[n_y][n_x] += 1

        # get it normalized
        ind = (weit > 0)
        image[ind] = temp_image[ind] / weit[ind]

        return image

    def curve(self, x, y, dy=None, color=0, symbol=0, label=None):
        """Draw a line on a graph, possibly with confidence intervals."""
        c = self._color(color)
        self.subplot.set_yscale('linear')
        self.subplot.set_xscale('linear')

        self.subplot.plot(x, y, color=c, marker='',
                          linestyle='-', label=label)
        self.subplot.set_yscale(self.yscale)
        self.subplot.set_xscale(self.xscale)

    def _color(self, c):
        """Return a particular colour"""
        return self.colorlist[c % len(self.colorlist)]

    def _symbol(self, s):
        """Return a particular symbol"""
        return self.symbollist[s % len(self.symbollist)]

    def _replot(self, remove_fit=False):
        """
        Rescale the plottables according to the latest
        user selection and update the plot

        :param remove_fit: Fit line will be removed if True

        """
        self.graph.reset_scale()
        self._onEVT_FUNC_PROPERTY(remove_fit=remove_fit)
        #TODO: Why do we have to have the following line?
        self.fit_result.reset_view()
        self.graph.render(self)
        self.subplot.figure.canvas.draw_idle()

    def _onEVT_FUNC_PROPERTY(self, remove_fit=True, show=True):
        """
        Receive the x and y transformation from myDialog,
        Transforms x and y in View
        and set the scale
        """
        # The logic should be in the right order
        # Delete first, and then get the whole list...
        if remove_fit:
            self.graph.delete(self.fit_result)
            if hasattr(self, 'plots'):
                if 'fit' in self.plots:
                    del self.plots['fit']
        self.ly = None
        self.q_ctrl = None
        list = self.graph.returnPlottable()
        # Changing the scale might be incompatible with
        # currently displayed data (for instance, going
        # from ln to log when all plotted values have
        # negative natural logs).
        # Go linear and only change the scale at the end.
        self.set_xscale("linear")
        self.set_yscale("linear")
        _xscale = 'linear'
        _yscale = 'linear'
        for item in list:
            if item.id == 'fit':
                continue
            item.setLabel(self.xLabel, self.yLabel)
            # control axis labels from the panel itself
            yname, yunits = item.get_yaxis()
            if self.yaxis_label is not None:
                yname = self.yaxis_label
                yunits = self.yaxis_unit
            else:
                self.yaxis_label = yname
                self.yaxis_unit = yunits
            xname, xunits = item.get_xaxis()
            if self.xaxis_label is not None:
                xname = self.xaxis_label
                xunits = self.xaxis_unit
            else:
                self.xaxis_label = xname
                self.xaxis_unit = xunits
            # Goes through all possible scales
            if self.xLabel == "x":
                item.transformX(transform.toX, transform.errToX)
                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
            if self.xLabel == "x^(2)":
                item.transformX(transform.toX2, transform.errToX2)
                xunits = convert_unit(2, xunits)
                self.graph._xaxis_transformed("%s^2" % xname, "%s" % xunits)
            if self.xLabel == "x^(4)":
                item.transformX(transform.toX4, transform.errToX4)
                xunits = convert_unit(4, xunits)
                self.graph._xaxis_transformed("%s^4" % xname, "%s" % xunits)
            if self.xLabel == "ln(x)":
                item.transformX(transform.toLogX, transform.errToLogX)
                self.graph._xaxis_transformed(r"\ln(%s)" % xname, "%s" % xunits)
            if self.xLabel == "log10(x)":
                item.transformX(transform.toX_pos, transform.errToX_pos)
                _xscale = 'log'
                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
            if self.xLabel == "log10(x^(4))":
                item.transformX(transform.toX4, transform.errToX4)
                xunits = convert_unit(4, xunits)
                self.graph._xaxis_transformed("%s^4" % xname, "%s" % xunits)
                _xscale = 'log'
            if self.yLabel == "ln(y)":
                item.transformY(transform.toLogX, transform.errToLogX)
                self.graph._yaxis_transformed(r"\ln(%s)" % yname, "%s" % yunits)
            if self.yLabel == "y":
                item.transformY(transform.toX, transform.errToX)
                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
            if self.yLabel == "log10(y)":
                item.transformY(transform.toX_pos, transform.errToX_pos)
                _yscale = 'log'
                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
            if self.yLabel == "y^(2)":
                item.transformY(transform.toX2, transform.errToX2)
                yunits = convert_unit(2, yunits)
                self.graph._yaxis_transformed("%s^{2}" % yname, "%s" % yunits)
            if self.yLabel == "1/y":
                item.transformY(transform.toOneOverX, transform.errOneOverX)
                yunits = convert_unit(-1, yunits)
                self.graph._yaxis_transformed("1/%s" % yname, "%s" % yunits)
            if self.yLabel == "y*x^(2)":
                item.transformY(transform.toYX2, transform.errToYX2)
                xunits = convert_unit(2, self.xaxis_unit)
                self.graph._yaxis_transformed(r"%s \ \ %s^2" % (yname, xname),
                                              "%s%s" % (yunits, xunits))
            if self.yLabel == "y*x^(4)":
                item.transformY(transform.toYX4, transform.errToYX4)
                xunits = convert_unit(4, self.xaxis_unit)
                self.graph._yaxis_transformed(r"%s \ \ %s^4" % (yname, xname),
                                              "%s%s" % (yunits, xunits))
            if self.yLabel == "1/sqrt(y)":
                item.transformY(transform.toOneOverSqrtX,
                                transform.errOneOverSqrtX)
                yunits = convert_unit(-0.5, yunits)
                self.graph._yaxis_transformed(r"1/\sqrt{%s}" % yname,
                                              "%s" % yunits)
            if self.yLabel == "ln(y*x)":
                item.transformY(transform.toLogXY, transform.errToLogXY)
                self.graph._yaxis_transformed(r"\ln(%s \ \ %s)" % (yname, xname),
                                              "%s%s" % (yunits, self.xaxis_unit))
            if self.yLabel == "ln(y*x^(2))":
                item.transformY(transform.toLogYX2, transform.errToLogYX2)
                xunits = convert_unit(2, self.xaxis_unit)
                self.graph._yaxis_transformed(r"\ln(%s \ \ %s^2)" % (yname, xname),
                                              "%s%s" % (yunits, xunits))
            if self.yLabel == "ln(y*x^(4))":
                item.transformY(transform.toLogYX4, transform.errToLogYX4)
                xunits = convert_unit(4, self.xaxis_unit)
                self.graph._yaxis_transformed(r"\ln(%s \ \ %s^4)" % (yname, xname),
                                              "%s%s" % (yunits, xunits))
            if self.yLabel == "log10(y*x^(4))":
                item.transformY(transform.toYX4, transform.errToYX4)
                xunits = convert_unit(4, self.xaxis_unit)
                _yscale = 'log'
                self.graph._yaxis_transformed(r"%s \ \ %s^4" % (yname, xname),
                                              "%s%s" % (yunits, xunits))
            item.transformView()

        # set new label and units
        yname = self.graph.prop["ylabel"]
        yunits = ''
        xname = self.graph.prop["xlabel"]
        xunits = ''

        self.resetFitView()
        self.prevXtrans = self.xLabel
        self.prevYtrans = self.yLabel
        self.graph.render(self)
        self.set_xscale(_xscale)
        self.set_yscale(_yscale)

        self.xaxis(xname, xunits, self.xaxis_font,
                   self.xaxis_color, self.xaxis_tick)
        self.yaxis(yname, yunits, self.yaxis_font,
                   self.yaxis_color, self.yaxis_tick)
        self.subplot.texts = self.textList
        if show:
            self.subplot.figure.canvas.draw_idle()

    def onFitDisplay(self, tempx, tempy, xminView,
                     xmaxView, xmin, xmax, func):
        """
        Add a new plottable into the graph .In this case this plottable
        will be used to fit some data

        :param tempx: The x data of fit line
        :param tempy: The y data of fit line
        :param xminView: the lower bound of fitting range
        :param xminView: the upper bound of  fitting range
        :param xmin: the lowest value of data to fit to the line
        :param xmax: the highest value of data to fit to the line

        """
        xlim = self.subplot.get_xlim()
        ylim = self.subplot.get_ylim()

        # Saving value to redisplay in Fit Dialog when it is opened again
        self.Avalue, self.Bvalue, self.ErrAvalue, \
            self.ErrBvalue, self.Chivalue = func
        self.xminView = xminView
        self.xmaxView = xmaxView
        self.xmin = xmin
        self.xmax = xmax
        #In case need to change the range of data plotted
        for item in self.graph.returnPlottable():
            item.onFitRange(None, None)
        # Create new data plottable with result
        self.fit_result.x = []
        self.fit_result.y = []
        self.fit_result.x = tempx
        self.fit_result.y = tempy
        self.fit_result.dx = None
        self.fit_result.dy = None
        #Load the view with the new values
        self.fit_result.reset_view()
        # Add the new plottable to the graph
        self.graph.add(self.fit_result)
        self.graph.render(self)
        self._offset_graph()
        if hasattr(self, 'plots'):
            # Used by Plotter1D
            fit_id = 'fit'
            self.fit_result.id = fit_id
            self.fit_result.title = 'Fit'
            self.fit_result.name = 'Fit'
            self.plots[fit_id] = self.fit_result
        self.subplot.set_xlim(xlim)
        self.subplot.set_ylim(ylim)
        self.subplot.figure.canvas.draw_idle()

    def onChangeCaption(self, event):
        """
        """
        if self.parent is None:
            return
        # get current caption
        old_caption = self.window_caption
        # Get new caption dialog
        dial = LabelDialog(None, -1, 'Modify Window Title', old_caption)
        if dial.ShowModal() == wx.ID_OK:
            new_caption = dial.getText()

            # send to guiframe to change the panel caption
            caption = self.parent.on_change_caption(self.window_name,
                                                    old_caption, new_caption)

            # also set new caption in plot_panels list
            self.parent.plot_panels[self.uid].window_caption = caption
            # set new caption
            self.window_caption = caption

        dial.Destroy()

    def onResetGraph(self, event):
        """
        Reset the graph by plotting the full range of data
        """
        for item in self.graph.returnPlottable():
            item.onReset()
        self.graph.render(self)
        self._onEVT_FUNC_PROPERTY(False)
        self.is_zoomed = False
        self.toolbar.update()

    def onPrint(self, event=None):
        self.toolbar.print_figure(event)

    def onPrinterSetup(self, event=None):
        """
        """
        self.canvas.Printer_Setup(event=event)
        self.Update()

    def onPrinterPreview(self, event=None):
        """
        Matplotlib camvas can no longer print itself.  Thus need to do
        everything ourselves: need to create a printpreview frame to to
        see the preview but needs a parent frame object.  Also needs a
        printout object (just as any printing task).
        """
        try:
            #check if parent is a frame.  If not keep getting the higher
            #parent till we find a frame
            _plot = self
            while not isinstance(_plot, wx.Frame):
                _plot = _plot.GetParent()
                assert _plot is not None

            #now create the printpeview object
            _preview = wx.PrintPreview(PlotPrintout(self.canvas),
                                       PlotPrintout(self.canvas))
            #and tie it to a printpreview frame then show it
            _frame = wx.PreviewFrame(_preview, _plot, "Print Preview", wx.Point(100, 100), wx.Size(600, 650))
            _frame.Centre(wx.BOTH)
            _frame.Initialize()
            _frame.Show(True)
        except:
            traceback.print_exc()
            pass

    def OnCopyFigureMenu(self, evt):
        """
        Copy the current figure to clipboard
        """
        try:
            self.toolbar.copy_figure(self.canvas)
        except:
            print("Error in copy Image")


#---------------------------------------------------------------
class NoRepaintCanvas(FigureCanvasWxAgg):
    """
    We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
    the draw method is only called for the first two paint events. After that,
    the canvas will only be redrawn when it is resized.

    """
    def __init__(self, *args, **kwargs):
        """
        """
        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
        self._drawn = 0

    def _onPaint(self, evt):
        """
        Called when wxPaintEvt is generated

        """
        if not self._isRealized:
            self.realize()
        if self._drawn < 2:
            self.draw(repaint=False)
            self._drawn += 1
        self.gui_repaint(drawDC=wx.PaintDC(self))

matplotlib.use matplotlib.font_manager.FontProperties matplotlib.backends.backend_wxagg.FigureCanvasWxAgg.__init__ matplotlib.interactive matplotlib.figure.Figure