controls/DebugVariablePanel/DebugVariableGraphicViewer.py
changeset 1730 64d8f52bc8c8
parent 1571 486f94a8032c
child 1734 750eeb7230a1
equal deleted inserted replaced
1726:d51af006fa6b 1730:64d8f52bc8c8
    81         # Update minimal range value
    81         # Update minimal range value
    82         if min_value is None:
    82         if min_value is None:
    83             min_value = range_min
    83             min_value = range_min
    84         elif range_min is not None:
    84         elif range_min is not None:
    85             min_value = min(min_value, range_min)
    85             min_value = min(min_value, range_min)
    86         
    86 
    87         # Update maximal range value
    87         # Update maximal range value
    88         if max_value is None:
    88         if max_value is None:
    89             max_value = range_max
    89             max_value = range_max
    90         elif range_min is not None:
    90         elif range_min is not None:
    91             max_value = max(max_value, range_max)
    91             max_value = max(max_value, range_max)
    92     
    92 
    93     # Calculate range center and width if at least one valid range is defined
    93     # Calculate range center and width if at least one valid range is defined
    94     if min_value is not None and max_value is not None:
    94     if min_value is not None and max_value is not None:
    95         center = (min_value + max_value) / 2.
    95         center = (min_value + max_value) / 2.
    96         range_size = max(1.0, max_value - min_value)
    96         range_size = max(1.0, max_value - min_value)
    97     
    97 
    98     # Set default center and with if no valid range is defined
    98     # Set default center and with if no valid range is defined
    99     else:
    99     else:
   100         center = 0.5
   100         center = 0.5
   101         range_size = 1.0
   101         range_size = 1.0
   102     
   102 
   103     # Return range expended from 10 %
   103     # Return range expended from 10 %
   104     return center - range_size * 0.55, center + range_size * 0.55
   104     return center - range_size * 0.55, center + range_size * 0.55
   105 
   105 
   106 #-------------------------------------------------------------------------------
   106 #-------------------------------------------------------------------------------
   107 #                   Debug Variable Graphic Viewer Drop Target
   107 #                   Debug Variable Graphic Viewer Drop Target
   111 Class that implements a custom drop target class for Debug Variable Graphic
   111 Class that implements a custom drop target class for Debug Variable Graphic
   112 Viewer
   112 Viewer
   113 """
   113 """
   114 
   114 
   115 class DebugVariableGraphicDropTarget(wx.TextDropTarget):
   115 class DebugVariableGraphicDropTarget(wx.TextDropTarget):
   116     
   116 
   117     def __init__(self, parent, window):
   117     def __init__(self, parent, window):
   118         """
   118         """
   119         Constructor
   119         Constructor
   120         @param parent: Reference to Debug Variable Graphic Viewer
   120         @param parent: Reference to Debug Variable Graphic Viewer
   121         @param window: Reference to the Debug Variable Panel
   121         @param window: Reference to the Debug Variable Panel
   122         """
   122         """
   123         wx.TextDropTarget.__init__(self)
   123         wx.TextDropTarget.__init__(self)
   124         self.ParentControl = parent
   124         self.ParentControl = parent
   125         self.ParentWindow = window
   125         self.ParentWindow = window
   126         
   126 
   127     def __del__(self):
   127     def __del__(self):
   128         """
   128         """
   129         Destructor
   129         Destructor
   130         """
   130         """
   131         # Remove reference to Debug Variable Graphic Viewer and Debug Variable
   131         # Remove reference to Debug Variable Graphic Viewer and Debug Variable
   132         # Panel
   132         # Panel
   133         self.ParentControl = None
   133         self.ParentControl = None
   134         self.ParentWindow = None
   134         self.ParentWindow = None
   135         
   135 
   136     def OnDragOver(self, x, y, d):
   136     def OnDragOver(self, x, y, d):
   137         """
   137         """
   138         Function called when mouse is dragged over Drop Target
   138         Function called when mouse is dragged over Drop Target
   139         @param x: X coordinate of mouse pointer
   139         @param x: X coordinate of mouse pointer
   140         @param y: Y coordinate of mouse pointer
   140         @param y: Y coordinate of mouse pointer
   141         @param d: Suggested default for return value
   141         @param d: Suggested default for return value
   142         """
   142         """
   143         # Signal parent that mouse is dragged over
   143         # Signal parent that mouse is dragged over
   144         self.ParentControl.OnMouseDragging(x, y)
   144         self.ParentControl.OnMouseDragging(x, y)
   145         
   145 
   146         return wx.TextDropTarget.OnDragOver(self, x, y, d)
   146         return wx.TextDropTarget.OnDragOver(self, x, y, d)
   147         
   147 
   148     def OnDropText(self, x, y, data):
   148     def OnDropText(self, x, y, data):
   149         """
   149         """
   150         Function called when mouse is released in Drop Target
   150         Function called when mouse is released in Drop Target
   151         @param x: X coordinate of mouse pointer
   151         @param x: X coordinate of mouse pointer
   152         @param y: Y coordinate of mouse pointer
   152         @param y: Y coordinate of mouse pointer
   153         @param data: Text associated to drag'n drop
   153         @param data: Text associated to drag'n drop
   154         """
   154         """
   155         # Signal Debug Variable Panel to reset highlight
   155         # Signal Debug Variable Panel to reset highlight
   156         self.ParentWindow.ResetHighlight()
   156         self.ParentWindow.ResetHighlight()
   157         
   157 
   158         message = None
   158         message = None
   159         
   159 
   160         # Check that data is valid regarding DebugVariablePanel
   160         # Check that data is valid regarding DebugVariablePanel
   161         try:
   161         try:
   162             values = eval(data)
   162             values = eval(data)
   163             if not isinstance(values, TupleType):
   163             if not isinstance(values, TupleType):
   164                 raise ValueError
   164                 raise ValueError
   165         except:
   165         except:
   166             message = _("Invalid value \"%s\" for debug variable")%data
   166             message = _("Invalid value \"%s\" for debug variable")%data
   167             values = None
   167             values = None
   168         
   168 
   169         # Display message if data is invalid
   169         # Display message if data is invalid
   170         if message is not None:
   170         if message is not None:
   171             wx.CallAfter(self.ShowMessage, message)
   171             wx.CallAfter(self.ShowMessage, message)
   172         
   172 
   173         # Data contain a reference to a variable to debug
   173         # Data contain a reference to a variable to debug
   174         elif values[1] == "debug":
   174         elif values[1] == "debug":
   175             target_idx = self.ParentControl.GetIndex()
   175             target_idx = self.ParentControl.GetIndex()
   176             
   176 
   177             # If mouse is dropped in graph canvas bounding box and graph is
   177             # If mouse is dropped in graph canvas bounding box and graph is
   178             # not 3D canvas, graphs will be merged
   178             # not 3D canvas, graphs will be merged
   179             rect = self.ParentControl.GetAxesBoundingBox()
   179             rect = self.ParentControl.GetAxesBoundingBox()
   180             if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
   180             if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
   181                 # Default merge type is parallel
   181                 # Default merge type is parallel
   182                 merge_type = GRAPH_PARALLEL
   182                 merge_type = GRAPH_PARALLEL
   183                 
   183 
   184                 # If mouse is dropped in left part of graph canvas, graph
   184                 # If mouse is dropped in left part of graph canvas, graph
   185                 # wall be merged orthogonally
   185                 # wall be merged orthogonally
   186                 merge_rect = wx.Rect(rect.x, rect.y, 
   186                 merge_rect = wx.Rect(rect.x, rect.y,
   187                                      rect.width / 2., rect.height)
   187                                      rect.width / 2., rect.height)
   188                 if merge_rect.InsideXY(x, y):
   188                 if merge_rect.InsideXY(x, y):
   189                     merge_type = GRAPH_ORTHOGONAL
   189                     merge_type = GRAPH_ORTHOGONAL
   190                 
   190 
   191                 # Merge graphs
   191                 # Merge graphs
   192                 wx.CallAfter(self.ParentWindow.MergeGraphs, 
   192                 wx.CallAfter(self.ParentWindow.MergeGraphs,
   193                              values[0], target_idx, 
   193                              values[0], target_idx,
   194                              merge_type, force=True)
   194                              merge_type, force=True)
   195                 
   195 
   196             else:
   196             else:
   197                 width, height = self.ParentControl.GetSize()
   197                 width, height = self.ParentControl.GetSize()
   198                 
   198 
   199                 # Get Before which Viewer the variable has to be moved or added
   199                 # Get Before which Viewer the variable has to be moved or added
   200                 # according to the position of mouse in Viewer.
   200                 # according to the position of mouse in Viewer.
   201                 if y > height / 2:
   201                 if y > height / 2:
   202                     target_idx += 1
   202                     target_idx += 1
   203                 
   203 
   204                 # Drag'n Drop is an internal is an internal move inside Debug
   204                 # Drag'n Drop is an internal is an internal move inside Debug
   205                 # Variable Panel 
   205                 # Variable Panel
   206                 if len(values) > 2 and values[2] == "move":
   206                 if len(values) > 2 and values[2] == "move":
   207                     self.ParentWindow.MoveValue(values[0], 
   207                     self.ParentWindow.MoveValue(values[0],
   208                                                 target_idx)
   208                                                 target_idx)
   209                 
   209 
   210                 # Drag'n Drop was initiated by another control of Beremiz
   210                 # Drag'n Drop was initiated by another control of Beremiz
   211                 else:
   211                 else:
   212                     self.ParentWindow.InsertValue(values[0], 
   212                     self.ParentWindow.InsertValue(values[0],
   213                                                   target_idx, 
   213                                                   target_idx,
   214                                                   force=True)
   214                                                   force=True)
   215     
   215 
   216     def OnLeave(self):
   216     def OnLeave(self):
   217         """
   217         """
   218         Function called when mouse is leave Drop Target
   218         Function called when mouse is leave Drop Target
   219         """
   219         """
   220         # Signal Debug Variable Panel to reset highlight
   220         # Signal Debug Variable Panel to reset highlight
   221         self.ParentWindow.ResetHighlight()
   221         self.ParentWindow.ResetHighlight()
   222         return wx.TextDropTarget.OnLeave(self)
   222         return wx.TextDropTarget.OnLeave(self)
   223     
   223 
   224     def ShowMessage(self, message):
   224     def ShowMessage(self, message):
   225         """
   225         """
   226         Show error message in Error Dialog
   226         Show error message in Error Dialog
   227         @param message: Error message to display
   227         @param message: Error message to display
   228         """
   228         """
   229         dialog = wx.MessageDialog(self.ParentWindow, 
   229         dialog = wx.MessageDialog(self.ParentWindow,
   230                                   message, 
   230                                   message,
   231                                   _("Error"), 
   231                                   _("Error"),
   232                                   wx.OK|wx.ICON_ERROR)
   232                                   wx.OK|wx.ICON_ERROR)
   233         dialog.ShowModal()
   233         dialog.ShowModal()
   234         dialog.Destroy()
   234         dialog.Destroy()
   235 
   235 
   236 
   236 
   241 """
   241 """
   242 Class that implements a Viewer that display variable values as a graphs
   242 Class that implements a Viewer that display variable values as a graphs
   243 """
   243 """
   244 
   244 
   245 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
   245 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
   246     
   246 
   247     def __init__(self, parent, window, items, graph_type):
   247     def __init__(self, parent, window, items, graph_type):
   248         """
   248         """
   249         Constructor
   249         Constructor
   250         @param parent: Parent wx.Window of DebugVariableText
   250         @param parent: Parent wx.Window of DebugVariableText
   251         @param window: Reference to the Debug Variable Panel
   251         @param window: Reference to the Debug Variable Panel
   252         @param items: List of DebugVariableItem displayed by Viewer
   252         @param items: List of DebugVariableItem displayed by Viewer
   253         @param graph_type: Graph display type (Parallel or orthogonal)
   253         @param graph_type: Graph display type (Parallel or orthogonal)
   254         """
   254         """
   255         DebugVariableViewer.__init__(self, window, items)
   255         DebugVariableViewer.__init__(self, window, items)
   256         
   256 
   257         self.GraphType = graph_type        # Graph type display
   257         self.GraphType = graph_type        # Graph type display
   258         self.CursorTick = None             # Tick of the graph cursor
   258         self.CursorTick = None             # Tick of the graph cursor
   259         
   259 
   260         # Mouse position when start dragging
   260         # Mouse position when start dragging
   261         self.MouseStartPos = None
   261         self.MouseStartPos = None
   262         # Tick when moving tick start
   262         # Tick when moving tick start
   263         self.StartCursorTick = None
   263         self.StartCursorTick = None
   264         # Canvas size when starting to resize canvas
   264         # Canvas size when starting to resize canvas
   265         self.CanvasStartSize = None        
   265         self.CanvasStartSize = None
   266         
   266 
   267         # List of current displayed contextual buttons
   267         # List of current displayed contextual buttons
   268         self.ContextualButtons = []
   268         self.ContextualButtons = []
   269         # Reference to item for which contextual buttons was displayed
   269         # Reference to item for which contextual buttons was displayed
   270         self.ContextualButtonsItem = None
   270         self.ContextualButtonsItem = None
   271         
   271 
   272         # Flag indicating that zoom fit current displayed data range or whole
   272         # Flag indicating that zoom fit current displayed data range or whole
   273         # data range if False
   273         # data range if False
   274         self.ZoomFit = False
   274         self.ZoomFit = False
   275         
   275 
   276         # Create figure for drawing graphs
   276         # Create figure for drawing graphs
   277         self.Figure = matplotlib.figure.Figure(facecolor='w')
   277         self.Figure = matplotlib.figure.Figure(facecolor='w')
   278         # Defined border around figure in canvas
   278         # Defined border around figure in canvas
   279         self.Figure.subplotpars.update(top=0.95, left=0.1, 
   279         self.Figure.subplotpars.update(top=0.95, left=0.1,
   280                                        bottom=0.1, right=0.95)
   280                                        bottom=0.1, right=0.95)
   281         
   281 
   282         FigureCanvas.__init__(self, parent, -1, self.Figure)
   282         FigureCanvas.__init__(self, parent, -1, self.Figure)
   283         self.SetWindowStyle(wx.WANTS_CHARS)
   283         self.SetWindowStyle(wx.WANTS_CHARS)
   284         self.SetBackgroundColour(wx.WHITE)
   284         self.SetBackgroundColour(wx.WHITE)
   285         
   285 
   286         # Bind wx events
   286         # Bind wx events
   287         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
   287         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
   288         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
   288         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
   289         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
   289         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
   290         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
   290         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
   291         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
   291         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
   292         self.Bind(wx.EVT_SIZE, self.OnResize)
   292         self.Bind(wx.EVT_SIZE, self.OnResize)
   293         
   293 
   294         # Set canvas min size
   294         # Set canvas min size
   295         canvas_size = self.GetCanvasMinSize()
   295         canvas_size = self.GetCanvasMinSize()
   296         self.SetMinSize(canvas_size)
   296         self.SetMinSize(canvas_size)
   297         
   297 
   298         # Define Viewer drop target
   298         # Define Viewer drop target
   299         self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
   299         self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
   300         
   300 
   301         # Connect matplotlib events
   301         # Connect matplotlib events
   302         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
   302         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
   303         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
   303         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
   304         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
   304         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
   305         self.mpl_connect('scroll_event', self.OnCanvasScroll)
   305         self.mpl_connect('scroll_event', self.OnCanvasScroll)
   306         
   306 
   307         # Add buttons for zooming on current displayed data range
   307         # Add buttons for zooming on current displayed data range
   308         self.Buttons.append(
   308         self.Buttons.append(
   309                 GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
   309                 GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
   310         
   310 
   311         # Add buttons for changing canvas size with predefined height
   311         # Add buttons for changing canvas size with predefined height
   312         for size, bitmap in zip(
   312         for size, bitmap in zip(
   313                 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
   313                 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
   314                 ["minimize_graph", "middle_graph", "maximize_graph"]):
   314                 ["minimize_graph", "middle_graph", "maximize_graph"]):
   315             self.Buttons.append(
   315             self.Buttons.append(
   316                     GraphButton(0, 0, bitmap, 
   316                     GraphButton(0, 0, bitmap,
   317                                 self.GetOnChangeSizeButton(size)))
   317                                 self.GetOnChangeSizeButton(size)))
   318         
   318 
   319         # Add buttons for exporting graph values to clipboard and close graph
   319         # Add buttons for exporting graph values to clipboard and close graph
   320         for bitmap, callback in [
   320         for bitmap, callback in [
   321                 ("export_graph_mini", self.OnExportGraphButton),
   321                 ("export_graph_mini", self.OnExportGraphButton),
   322                 ("delete_graph", self.OnCloseButton)]:
   322                 ("delete_graph", self.OnCloseButton)]:
   323             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
   323             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
   324         
   324 
   325         # Update graphs elements
   325         # Update graphs elements
   326         self.ResetGraphics()
   326         self.ResetGraphics()
   327         self.RefreshLabelsPosition(canvas_size.height)
   327         self.RefreshLabelsPosition(canvas_size.height)
   328     
   328 
   329     def AddItem(self, item):
   329     def AddItem(self, item):
   330         """
   330         """
   331         Add an item to the list of items displayed by Viewer
   331         Add an item to the list of items displayed by Viewer
   332         @param item: Item to add to the list
   332         @param item: Item to add to the list
   333         """
   333         """
   334         DebugVariableViewer.AddItem(self, item)
   334         DebugVariableViewer.AddItem(self, item)
   335         self.ResetGraphics()
   335         self.ResetGraphics()
   336         
   336 
   337     def RemoveItem(self, item):
   337     def RemoveItem(self, item):
   338         """
   338         """
   339         Remove an item from the list of items displayed by Viewer
   339         Remove an item from the list of items displayed by Viewer
   340         @param item: Item to remove from the list
   340         @param item: Item to remove from the list
   341         """
   341         """
   342         DebugVariableViewer.RemoveItem(self, item)
   342         DebugVariableViewer.RemoveItem(self, item)
   343         
   343 
   344         # If list of items is not empty
   344         # If list of items is not empty
   345         if not self.ItemsIsEmpty():
   345         if not self.ItemsIsEmpty():
   346             # Return to parallel graph if there is only one item
   346             # Return to parallel graph if there is only one item
   347             # especially if it's actually orthogonal
   347             # especially if it's actually orthogonal
   348             if len(self.Items) == 1:
   348             if len(self.Items) == 1:
   349                 self.GraphType = GRAPH_PARALLEL
   349                 self.GraphType = GRAPH_PARALLEL
   350             self.ResetGraphics()
   350             self.ResetGraphics()
   351     
   351 
   352     def SetCursorTick(self, cursor_tick):
   352     def SetCursorTick(self, cursor_tick):
   353         """
   353         """
   354         Set cursor tick
   354         Set cursor tick
   355         @param cursor_tick: Cursor tick
   355         @param cursor_tick: Cursor tick
   356         """
   356         """
   357         self.CursorTick = cursor_tick
   357         self.CursorTick = cursor_tick
   358     
   358 
   359     def SetZoomFit(self, zoom_fit):
   359     def SetZoomFit(self, zoom_fit):
   360         """
   360         """
   361         Set flag indicating that zoom fit current displayed data range
   361         Set flag indicating that zoom fit current displayed data range
   362         @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
   362         @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
   363         """
   363         """
   364         # Flag is different from the actual one 
   364         # Flag is different from the actual one
   365         if zoom_fit != self.ZoomFit:
   365         if zoom_fit != self.ZoomFit:
   366             # Save new flag value
   366             # Save new flag value
   367             self.ZoomFit = zoom_fit
   367             self.ZoomFit = zoom_fit
   368             
   368 
   369             # Update button for zoom fit bitmap
   369             # Update button for zoom fit bitmap
   370             self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
   370             self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
   371             
   371 
   372             # Refresh canvas
   372             # Refresh canvas
   373             self.RefreshViewer()
   373             self.RefreshViewer()
   374         
   374 
   375     def SubscribeAllDataConsumers(self):
   375     def SubscribeAllDataConsumers(self):
   376         """
   376         """
   377         Function that unsubscribe and remove every item that store values of
   377         Function that unsubscribe and remove every item that store values of
   378         a variable that doesn't exist in PLC anymore
   378         a variable that doesn't exist in PLC anymore
   379         """
   379         """
   380         DebugVariableViewer.SubscribeAllDataConsumers(self)
   380         DebugVariableViewer.SubscribeAllDataConsumers(self)
   381         
   381 
   382         # Graph still have data to display
   382         # Graph still have data to display
   383         if not self.ItemsIsEmpty():
   383         if not self.ItemsIsEmpty():
   384             # Reset flag indicating that zoom fit current displayed data range
   384             # Reset flag indicating that zoom fit current displayed data range
   385             self.SetZoomFit(False)
   385             self.SetZoomFit(False)
   386             
   386 
   387             self.ResetGraphics()
   387             self.ResetGraphics()
   388     
   388 
   389     def Is3DCanvas(self):
   389     def Is3DCanvas(self):
   390         """
   390         """
   391         Return if Viewer is a 3D canvas
   391         Return if Viewer is a 3D canvas
   392         @return: True if Viewer is a 3D canvas
   392         @return: True if Viewer is a 3D canvas
   393         """
   393         """
   394         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
   394         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
   395     
   395 
   396     def GetButtons(self):
   396     def GetButtons(self):
   397         """
   397         """
   398         Return list of buttons defined in Viewer
   398         Return list of buttons defined in Viewer
   399         @return: List of buttons
   399         @return: List of buttons
   400         """
   400         """
   401         # Add contextual buttons to default buttons
   401         # Add contextual buttons to default buttons
   402         return self.Buttons + self.ContextualButtons
   402         return self.Buttons + self.ContextualButtons
   403     
   403 
   404     def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
   404     def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
   405         """
   405         """
   406         Show contextual menu for item aside a label of this item defined
   406         Show contextual menu for item aside a label of this item defined
   407         by the bounding box of label in figure
   407         by the bounding box of label in figure
   408         @param item: Item for which contextual is shown
   408         @param item: Item for which contextual is shown
   410         @param direction: Direction in which buttons must be drawn
   410         @param direction: Direction in which buttons must be drawn
   411         """
   411         """
   412         # Return immediately if contextual menu for item is already shown
   412         # Return immediately if contextual menu for item is already shown
   413         if self.ContextualButtonsItem == item:
   413         if self.ContextualButtonsItem == item:
   414             return
   414             return
   415         
   415 
   416         # Close already shown contextual menu
   416         # Close already shown contextual menu
   417         self.DismissContextualButtons()
   417         self.DismissContextualButtons()
   418         
   418 
   419         # Save item for which contextual menu is shown
   419         # Save item for which contextual menu is shown
   420         self.ContextualButtonsItem = item
   420         self.ContextualButtonsItem = item
   421         
   421 
   422         # If item variable is forced, add button for release variable to
   422         # If item variable is forced, add button for release variable to
   423         # contextual menu
   423         # contextual menu
   424         if self.ContextualButtonsItem.IsForced():
   424         if self.ContextualButtonsItem.IsForced():
   425             self.ContextualButtons.append(
   425             self.ContextualButtons.append(
   426                 GraphButton(0, 0, "release", self.OnReleaseItemButton))
   426                 GraphButton(0, 0, "release", self.OnReleaseItemButton))
   427         
   427 
   428         # Add other buttons to contextual menu
   428         # Add other buttons to contextual menu
   429         for bitmap, callback in [
   429         for bitmap, callback in [
   430                 ("force", self.OnForceItemButton),
   430                 ("force", self.OnForceItemButton),
   431                 ("export_graph_mini", self.OnExportItemGraphButton),
   431                 ("export_graph_mini", self.OnExportItemGraphButton),
   432                 ("delete_graph", self.OnRemoveItemButton)]:
   432                 ("delete_graph", self.OnRemoveItemButton)]:
   433             self.ContextualButtons.append(
   433             self.ContextualButtons.append(
   434                     GraphButton(0, 0, bitmap, callback))
   434                     GraphButton(0, 0, bitmap, callback))
   435         
   435 
   436         # If buttons are shown at left side or upper side of rect, positions
   436         # If buttons are shown at left side or upper side of rect, positions
   437         # will be set in reverse order
   437         # will be set in reverse order
   438         buttons = self.ContextualButtons[:]
   438         buttons = self.ContextualButtons[:]
   439         if direction in [wx.TOP, wx.LEFT]:
   439         if direction in [wx.TOP, wx.LEFT]:
   440              buttons.reverse()
   440              buttons.reverse()
   441              
   441 
   442         # Set contextual menu buttons position aside rect depending on
   442         # Set contextual menu buttons position aside rect depending on
   443         # direction given
   443         # direction given
   444         offset = 0
   444         offset = 0
   445         for button in buttons:
   445         for button in buttons:
   446             w, h = button.GetSize()
   446             w, h = button.GetSize()
   456                               if direction == wx.TOP
   456                               if direction == wx.TOP
   457                               else rect.height + offset)
   457                               else rect.height + offset)
   458                 offset += h
   458                 offset += h
   459             button.SetPosition(x, y)
   459             button.SetPosition(x, y)
   460             button.Show()
   460             button.Show()
   461         
   461 
   462         # Refresh canvas
   462         # Refresh canvas
   463         self.ParentWindow.ForceRefresh()
   463         self.ParentWindow.ForceRefresh()
   464     
   464 
   465     def DismissContextualButtons(self):
   465     def DismissContextualButtons(self):
   466         """
   466         """
   467         Close current shown contextual menu
   467         Close current shown contextual menu
   468         """
   468         """
   469         # Return immediately if no contextual menu is shown
   469         # Return immediately if no contextual menu is shown
   470         if self.ContextualButtonsItem is None:
   470         if self.ContextualButtonsItem is None:
   471             return
   471             return
   472         
   472 
   473         # Reset variables corresponding to contextual menu
   473         # Reset variables corresponding to contextual menu
   474         self.ContextualButtonsItem = None
   474         self.ContextualButtonsItem = None
   475         self.ContextualButtons = []
   475         self.ContextualButtons = []
   476         
   476 
   477         # Refresh canvas
   477         # Refresh canvas
   478         self.ParentWindow.ForceRefresh()
   478         self.ParentWindow.ForceRefresh()
   479     
   479 
   480     def IsOverContextualButton(self, x, y):
   480     def IsOverContextualButton(self, x, y):
   481         """
   481         """
   482         Return if point is over one contextual button of Viewer
   482         Return if point is over one contextual button of Viewer
   483         @param x: X coordinate of point
   483         @param x: X coordinate of point
   484         @param y: Y coordinate of point
   484         @param y: Y coordinate of point
   486         """
   486         """
   487         for button in self.ContextualButtons:
   487         for button in self.ContextualButtons:
   488             if button.HitTest(x, y):
   488             if button.HitTest(x, y):
   489                 return button
   489                 return button
   490         return None
   490         return None
   491     
   491 
   492     def ExportGraph(self, item=None):
   492     def ExportGraph(self, item=None):
   493         """
   493         """
   494         Export item(s) data to clipboard in CSV format
   494         Export item(s) data to clipboard in CSV format
   495         @param item: Item from which data to export, all items if None
   495         @param item: Item from which data to export, all items if None
   496         (default None)
   496         (default None)
   497         """
   497         """
   498         self.ParentWindow.CopyDataToClipboard(
   498         self.ParentWindow.CopyDataToClipboard(
   499             [(item, [entry for entry in item.GetData()])
   499             [(item, [entry for entry in item.GetData()])
   500              for item in (self.Items 
   500              for item in (self.Items
   501                           if item is None 
   501                           if item is None
   502                           else [item])])
   502                           else [item])])
   503     
   503 
   504     def OnZoomFitButton(self):
   504     def OnZoomFitButton(self):
   505         """
   505         """
   506         Function called when Viewer Zoom Fit button is pressed
   506         Function called when Viewer Zoom Fit button is pressed
   507         """
   507         """
   508         # Toggle zoom fit flag value
   508         # Toggle zoom fit flag value
   509         self.SetZoomFit(not self.ZoomFit)
   509         self.SetZoomFit(not self.ZoomFit)
   510         
   510 
   511     def GetOnChangeSizeButton(self, height):
   511     def GetOnChangeSizeButton(self, height):
   512         """
   512         """
   513         Function that generate callback function for change Viewer height to
   513         Function that generate callback function for change Viewer height to
   514         pre-defined height button
   514         pre-defined height button
   515         @param height: Height that change Viewer to
   515         @param height: Height that change Viewer to
   516         @return: callback function
   516         @return: callback function
   517         """
   517         """
   518         def OnChangeSizeButton():
   518         def OnChangeSizeButton():
   519             self.SetCanvasHeight(height)
   519             self.SetCanvasHeight(height)
   520         return OnChangeSizeButton
   520         return OnChangeSizeButton
   521     
   521 
   522     def OnExportGraphButton(self):
   522     def OnExportGraphButton(self):
   523         """
   523         """
   524         Function called when Viewer Export button is pressed
   524         Function called when Viewer Export button is pressed
   525         """
   525         """
   526         # Export data of every item in Viewer
   526         # Export data of every item in Viewer
   527         self.ExportGraph()
   527         self.ExportGraph()
   528     
   528 
   529     def OnForceItemButton(self):
   529     def OnForceItemButton(self):
   530         """
   530         """
   531         Function called when contextual menu Force button is pressed
   531         Function called when contextual menu Force button is pressed
   532         """
   532         """
   533         # Open dialog for forcing item variable value 
   533         # Open dialog for forcing item variable value
   534         self.ForceValue(self.ContextualButtonsItem)
   534         self.ForceValue(self.ContextualButtonsItem)
   535         # Close contextual menu
   535         # Close contextual menu
   536         self.DismissContextualButtons()
   536         self.DismissContextualButtons()
   537         
   537 
   538     def OnReleaseItemButton(self):
   538     def OnReleaseItemButton(self):
   539         """
   539         """
   540         Function called when contextual menu Release button is pressed
   540         Function called when contextual menu Release button is pressed
   541         """
   541         """
   542         # Release item variable value 
   542         # Release item variable value
   543         self.ReleaseValue(self.ContextualButtonsItem)
   543         self.ReleaseValue(self.ContextualButtonsItem)
   544         # Close contextual menu
   544         # Close contextual menu
   545         self.DismissContextualButtons()
   545         self.DismissContextualButtons()
   546     
   546 
   547     def OnExportItemGraphButton(self):
   547     def OnExportItemGraphButton(self):
   548         """
   548         """
   549         Function called when contextual menu Export button is pressed
   549         Function called when contextual menu Export button is pressed
   550         """
   550         """
   551         # Export data of item variable
   551         # Export data of item variable
   552         self.ExportGraph(self.ContextualButtonsItem)
   552         self.ExportGraph(self.ContextualButtonsItem)
   553         # Close contextual menu
   553         # Close contextual menu
   554         self.DismissContextualButtons()
   554         self.DismissContextualButtons()
   555         
   555 
   556     def OnRemoveItemButton(self):            
   556     def OnRemoveItemButton(self):
   557         """
   557         """
   558         Function called when contextual menu Remove button is pressed
   558         Function called when contextual menu Remove button is pressed
   559         """
   559         """
   560         # Remove item from Viewer
   560         # Remove item from Viewer
   561         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
   561         wx.CallAfter(self.ParentWindow.DeleteValue, self,
   562                      self.ContextualButtonsItem)
   562                      self.ContextualButtonsItem)
   563         # Close contextual menu
   563         # Close contextual menu
   564         self.DismissContextualButtons()
   564         self.DismissContextualButtons()
   565     
   565 
   566     def HandleCursorMove(self, event):
   566     def HandleCursorMove(self, event):
   567         """
   567         """
   568         Update Cursor position according to mouse position and graph type
   568         Update Cursor position according to mouse position and graph type
   569         @param event: Mouse event
   569         @param event: Mouse event
   570         """
   570         """
   571         start_tick, end_tick = self.ParentWindow.GetRange()
   571         start_tick, end_tick = self.ParentWindow.GetRange()
   572         cursor_tick = None
   572         cursor_tick = None
   573         items = self.ItemsDict.values()
   573         items = self.ItemsDict.values()
   574         
   574 
   575         # Graph is orthogonal
   575         # Graph is orthogonal
   576         if self.GraphType == GRAPH_ORTHOGONAL:
   576         if self.GraphType == GRAPH_ORTHOGONAL:
   577             # Extract items data displayed in canvas figure
   577             # Extract items data displayed in canvas figure
   578             start_tick = max(start_tick, self.GetItemsMinCommonTick())
   578             start_tick = max(start_tick, self.GetItemsMinCommonTick())
   579             end_tick = max(end_tick, start_tick)
   579             end_tick = max(end_tick, start_tick)
   580             x_data = items[0].GetData(start_tick, end_tick)
   580             x_data = items[0].GetData(start_tick, end_tick)
   581             y_data = items[1].GetData(start_tick, end_tick)
   581             y_data = items[1].GetData(start_tick, end_tick)
   582             
   582 
   583             # Search for the nearest point from mouse position
   583             # Search for the nearest point from mouse position
   584             if len(x_data) > 0 and len(y_data) > 0:
   584             if len(x_data) > 0 and len(y_data) > 0:
   585                 length = min(len(x_data), len(y_data)) 
   585                 length = min(len(x_data), len(y_data))
   586                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \
   586                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \
   587                                (y_data[:length,1]-event.ydata) ** 2)
   587                                (y_data[:length,1]-event.ydata) ** 2)
   588                 
   588 
   589                 # Set cursor tick to the tick of this point
   589                 # Set cursor tick to the tick of this point
   590                 cursor_tick = x_data[numpy.argmin(d), 0]
   590                 cursor_tick = x_data[numpy.argmin(d), 0]
   591         
   591 
   592         # Graph is parallel
   592         # Graph is parallel
   593         else:
   593         else:
   594             # Extract items tick
   594             # Extract items tick
   595             data = items[0].GetData(start_tick, end_tick)
   595             data = items[0].GetData(start_tick, end_tick)
   596             
   596 
   597             # Search for point that tick is the nearest from mouse X position
   597             # Search for point that tick is the nearest from mouse X position
   598             # and set cursor tick to the tick of this point
   598             # and set cursor tick to the tick of this point
   599             if len(data) > 0:
   599             if len(data) > 0:
   600                 cursor_tick = data[numpy.argmin(
   600                 cursor_tick = data[numpy.argmin(
   601                         numpy.abs(data[:,0] - event.xdata)), 0]
   601                         numpy.abs(data[:,0] - event.xdata)), 0]
   602         
   602 
   603         # Update cursor tick
   603         # Update cursor tick
   604         if cursor_tick is not None:
   604         if cursor_tick is not None:
   605             self.ParentWindow.SetCursorTick(cursor_tick)
   605             self.ParentWindow.SetCursorTick(cursor_tick)
   606     
   606 
   607     def OnCanvasButtonPressed(self, event):
   607     def OnCanvasButtonPressed(self, event):
   608         """
   608         """
   609         Function called when a button of mouse is pressed
   609         Function called when a button of mouse is pressed
   610         @param event: Mouse event
   610         @param event: Mouse event
   611         """
   611         """
   612         # Get mouse position, graph Y coordinate is inverted in matplotlib
   612         # Get mouse position, graph Y coordinate is inverted in matplotlib
   613         # comparing to wx
   613         # comparing to wx
   614         width, height = self.GetSize()
   614         width, height = self.GetSize()
   615         x, y = event.x, height - event.y
   615         x, y = event.x, height - event.y
   616         
   616 
   617         # Return immediately if mouse is over a button
   617         # Return immediately if mouse is over a button
   618         if self.IsOverButton(x, y):
   618         if self.IsOverButton(x, y):
   619             return 
   619             return
   620         
   620 
   621         # Mouse was clicked inside graph figure
   621         # Mouse was clicked inside graph figure
   622         if event.inaxes == self.Axes:
   622         if event.inaxes == self.Axes:
   623             
   623 
   624             # Find if it was on an item label
   624             # Find if it was on an item label
   625             item_idx = None
   625             item_idx = None
   626             # Check every label paired with corresponding item
   626             # Check every label paired with corresponding item
   627             for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
   627             for i, t in ([pair for pair in enumerate(self.AxesLabels)] +
   628                          [pair for pair in enumerate(self.Labels)]):
   628                          [pair for pair in enumerate(self.Labels)]):
   629                 # Get label bounding box
   629                 # Get label bounding box
   630                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
   630                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
   631                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
   631                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
   632                 # Check if mouse was over label
   632                 # Check if mouse was over label
   633                 if rect.InsideXY(x, y):
   633                 if rect.InsideXY(x, y):
   634                     item_idx = i
   634                     item_idx = i
   635                     break
   635                     break
   636             
   636 
   637             # If an item label have been clicked
   637             # If an item label have been clicked
   638             if item_idx is not None:
   638             if item_idx is not None:
   639                 # Hide buttons and contextual buttons
   639                 # Hide buttons and contextual buttons
   640                 self.ShowButtons(False)
   640                 self.ShowButtons(False)
   641                 self.DismissContextualButtons()
   641                 self.DismissContextualButtons()
   642                 
   642 
   643                 # Start a drag'n drop from mouse position in wx coordinate of
   643                 # Start a drag'n drop from mouse position in wx coordinate of
   644                 # parent
   644                 # parent
   645                 xw, yw = self.GetPosition()
   645                 xw, yw = self.GetPosition()
   646                 self.ParentWindow.StartDragNDrop(self, 
   646                 self.ParentWindow.StartDragNDrop(self,
   647                     self.ItemsDict.values()[item_idx], 
   647                     self.ItemsDict.values()[item_idx],
   648                     x + xw, y + yw, # Current mouse position
   648                     x + xw, y + yw, # Current mouse position
   649                     x + xw, y + yw) # Mouse position when button was clicked
   649                     x + xw, y + yw) # Mouse position when button was clicked
   650             
   650 
   651             # Don't handle mouse button if canvas is 3D and let matplotlib do
   651             # Don't handle mouse button if canvas is 3D and let matplotlib do
   652             # the default behavior (rotate 3D axes)
   652             # the default behavior (rotate 3D axes)
   653             elif not self.Is3DCanvas():
   653             elif not self.Is3DCanvas():
   654                 # Save mouse position when clicked
   654                 # Save mouse position when clicked
   655                 self.MouseStartPos = wx.Point(x, y)
   655                 self.MouseStartPos = wx.Point(x, y)
   656                 
   656 
   657                 # Mouse button was left button, start moving cursor
   657                 # Mouse button was left button, start moving cursor
   658                 if event.button == 1:
   658                 if event.button == 1:
   659                     # Save current tick in case a drag'n drop is initiate to
   659                     # Save current tick in case a drag'n drop is initiate to
   660                     # restore it
   660                     # restore it
   661                     self.StartCursorTick = self.CursorTick
   661                     self.StartCursorTick = self.CursorTick
   662                     
   662 
   663                     self.HandleCursorMove(event)
   663                     self.HandleCursorMove(event)
   664                     
   664 
   665                 # Mouse button is middle button and graph is parallel, start
   665                 # Mouse button is middle button and graph is parallel, start
   666                 # moving graph along X coordinate (tick)
   666                 # moving graph along X coordinate (tick)
   667                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
   667                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
   668                     self.StartCursorTick = self.ParentWindow.GetRange()[0]
   668                     self.StartCursorTick = self.ParentWindow.GetRange()[0]
   669         
   669 
   670         # Mouse was clicked outside graph figure and over resize highlight with
   670         # Mouse was clicked outside graph figure and over resize highlight with
   671         # left button, start resizing Viewer
   671         # left button, start resizing Viewer
   672         elif event.button == 1 and event.y <= 5:
   672         elif event.button == 1 and event.y <= 5:
   673             self.MouseStartPos = wx.Point(x, y)
   673             self.MouseStartPos = wx.Point(x, y)
   674             self.CanvasStartSize = height
   674             self.CanvasStartSize = height
   675     
   675 
   676     def OnCanvasButtonReleased(self, event):
   676     def OnCanvasButtonReleased(self, event):
   677         """
   677         """
   678         Function called when a button of mouse is released
   678         Function called when a button of mouse is released
   679         @param event: Mouse event
   679         @param event: Mouse event
   680         """
   680         """
   684             xw, yw = self.GetPosition()
   684             xw, yw = self.GetPosition()
   685             item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
   685             item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
   686             # Give mouse position in wx coordinate of parent
   686             # Give mouse position in wx coordinate of parent
   687             self.ParentWindow.StopDragNDrop(item.GetVariable(),
   687             self.ParentWindow.StopDragNDrop(item.GetVariable(),
   688                 xw + event.x, yw + height - event.y)
   688                 xw + event.x, yw + height - event.y)
   689         
   689 
   690         else:
   690         else:
   691             # Reset any move in progress
   691             # Reset any move in progress
   692             self.MouseStartPos = None
   692             self.MouseStartPos = None
   693             self.CanvasStartSize = None
   693             self.CanvasStartSize = None
   694             
   694 
   695             # Handle button under mouse if it exist
   695             # Handle button under mouse if it exist
   696             width, height = self.GetSize()
   696             width, height = self.GetSize()
   697             self.HandleButton(event.x, height - event.y)
   697             self.HandleButton(event.x, height - event.y)
   698     
   698 
   699     def OnCanvasMotion(self, event):
   699     def OnCanvasMotion(self, event):
   700         """
   700         """
   701         Function called when a button of mouse is moved over Viewer
   701         Function called when a button of mouse is moved over Viewer
   702         @param event: Mouse event
   702         @param event: Mouse event
   703         """
   703         """
   704         width, height = self.GetSize()
   704         width, height = self.GetSize()
   705         
   705 
   706         # If a drag'n drop is in progress, move canvas dragged
   706         # If a drag'n drop is in progress, move canvas dragged
   707         if self.ParentWindow.IsDragging():
   707         if self.ParentWindow.IsDragging():
   708             xw, yw = self.GetPosition()
   708             xw, yw = self.GetPosition()
   709             # Give mouse position in wx coordinate of parent
   709             # Give mouse position in wx coordinate of parent
   710             self.ParentWindow.MoveDragNDrop(
   710             self.ParentWindow.MoveDragNDrop(
   711                 xw + event.x, 
   711                 xw + event.x,
   712                 yw + height - event.y)
   712                 yw + height - event.y)
   713         
   713 
   714         # If a Viewer resize is in progress, change Viewer size 
   714         # If a Viewer resize is in progress, change Viewer size
   715         elif event.button == 1 and self.CanvasStartSize is not None:
   715         elif event.button == 1 and self.CanvasStartSize is not None:
   716             width, height = self.GetSize()
   716             width, height = self.GetSize()
   717             self.SetCanvasHeight(
   717             self.SetCanvasHeight(
   718                 self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
   718                 self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
   719         
   719 
   720         # If no button is pressed, show or hide contextual buttons or resize
   720         # If no button is pressed, show or hide contextual buttons or resize
   721         # highlight
   721         # highlight
   722         elif event.button is None:
   722         elif event.button is None:
   723             # Compute direction for items label according graph type
   723             # Compute direction for items label according graph type
   724             if self.GraphType == GRAPH_PARALLEL: # Graph is parallel
   724             if self.GraphType == GRAPH_PARALLEL: # Graph is parallel
   727             elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D
   727             elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D
   728                 directions = [wx.RIGHT, wx.TOP,  # Directions for AxesLabels
   728                 directions = [wx.RIGHT, wx.TOP,  # Directions for AxesLabels
   729                              wx.LEFT, wx.BOTTOM] # Directions for Labels
   729                              wx.LEFT, wx.BOTTOM] # Directions for Labels
   730             else: # Graph is orthogonal in 3D
   730             else: # Graph is orthogonal in 3D
   731                 directions = [wx.LEFT] * len(self.Labels)
   731                 directions = [wx.LEFT] * len(self.Labels)
   732             
   732 
   733             # Find if mouse is over an item label
   733             # Find if mouse is over an item label
   734             item_idx = None
   734             item_idx = None
   735             menu_direction = None
   735             menu_direction = None
   736             for (i, t), dir in zip(
   736             for (i, t), dir in zip(
   737                     [pair for pair in enumerate(self.AxesLabels)] + 
   737                     [pair for pair in enumerate(self.AxesLabels)] +
   738                     [pair for pair in enumerate(self.Labels)], 
   738                     [pair for pair in enumerate(self.Labels)],
   739                     directions):
   739                     directions):
   740                 # Check every label paired with corresponding item
   740                 # Check every label paired with corresponding item
   741                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
   741                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
   742                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
   742                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
   743                 # Check if mouse was over label
   743                 # Check if mouse was over label
   744                 if rect.InsideXY(event.x, height - event.y):
   744                 if rect.InsideXY(event.x, height - event.y):
   745                     item_idx = i
   745                     item_idx = i
   746                     menu_direction = dir
   746                     menu_direction = dir
   747                     break
   747                     break
   748             
   748 
   749             # If mouse is over an item label, 
   749             # If mouse is over an item label,
   750             if item_idx is not None:
   750             if item_idx is not None:
   751                 self.PopupContextualButtons(
   751                 self.PopupContextualButtons(
   752                     self.ItemsDict.values()[item_idx], 
   752                     self.ItemsDict.values()[item_idx],
   753                     rect, menu_direction)
   753                     rect, menu_direction)
   754                 return
   754                 return
   755             
   755 
   756             # If mouse isn't over a contextual menu, hide the current shown one
   756             # If mouse isn't over a contextual menu, hide the current shown one
   757             # if it exists 
   757             # if it exists
   758             if self.IsOverContextualButton(event.x, height - event.y) is None:
   758             if self.IsOverContextualButton(event.x, height - event.y) is None:
   759                 self.DismissContextualButtons()
   759                 self.DismissContextualButtons()
   760             
   760 
   761             # Update resize highlight
   761             # Update resize highlight
   762             if event.y <= 5:
   762             if event.y <= 5:
   763                 if self.SetHighlight(HIGHLIGHT_RESIZE):
   763                 if self.SetHighlight(HIGHLIGHT_RESIZE):
   764                     self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
   764                     self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
   765                     self.ParentWindow.ForceRefresh()
   765                     self.ParentWindow.ForceRefresh()
   766             else:
   766             else:
   767                 if self.SetHighlight(HIGHLIGHT_NONE):
   767                 if self.SetHighlight(HIGHLIGHT_NONE):
   768                     self.SetCursor(wx.NullCursor)
   768                     self.SetCursor(wx.NullCursor)
   769                     self.ParentWindow.ForceRefresh()
   769                     self.ParentWindow.ForceRefresh()
   770         
   770 
   771         # Handle buttons if canvas is not 3D 
   771         # Handle buttons if canvas is not 3D
   772         elif not self.Is3DCanvas():
   772         elif not self.Is3DCanvas():
   773             
   773 
   774             # If left button is pressed
   774             # If left button is pressed
   775             if event.button == 1:
   775             if event.button == 1:
   776                 
   776 
   777                 # Mouse is inside graph figure
   777                 # Mouse is inside graph figure
   778                 if event.inaxes == self.Axes:
   778                 if event.inaxes == self.Axes:
   779                     
   779 
   780                     # If a cursor move is in progress, update cursor position
   780                     # If a cursor move is in progress, update cursor position
   781                     if self.MouseStartPos is not None:
   781                     if self.MouseStartPos is not None:
   782                         self.HandleCursorMove(event)
   782                         self.HandleCursorMove(event)
   783                 
   783 
   784                 # Mouse is outside graph figure, cursor move is in progress and
   784                 # Mouse is outside graph figure, cursor move is in progress and
   785                 # there is only one item in Viewer, start a drag'n drop
   785                 # there is only one item in Viewer, start a drag'n drop
   786                 elif self.MouseStartPos is not None and len(self.Items) == 1:
   786                 elif self.MouseStartPos is not None and len(self.Items) == 1:
   787                     xw, yw = self.GetPosition()
   787                     xw, yw = self.GetPosition()
   788                     self.ParentWindow.SetCursorTick(self.StartCursorTick)
   788                     self.ParentWindow.SetCursorTick(self.StartCursorTick)
   789                     self.ParentWindow.StartDragNDrop(self, 
   789                     self.ParentWindow.StartDragNDrop(self,
   790                         self.ItemsDict.values()[0],
   790                         self.ItemsDict.values()[0],
   791                         # Current mouse position
   791                         # Current mouse position
   792                         event.x + xw, height - event.y + yw,
   792                         event.x + xw, height - event.y + yw,
   793                         # Mouse position when button was clicked
   793                         # Mouse position when button was clicked
   794                         self.MouseStartPos.x + xw,
   794                         self.MouseStartPos.x + xw,
   795                         self.MouseStartPos.y + yw)
   795                         self.MouseStartPos.y + yw)
   796             
   796 
   797             # If middle button is pressed and moving graph along X coordinate
   797             # If middle button is pressed and moving graph along X coordinate
   798             # is in progress
   798             # is in progress
   799             elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
   799             elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
   800                  self.MouseStartPos is not None:
   800                  self.MouseStartPos is not None:
   801                 start_tick, end_tick = self.ParentWindow.GetRange()
   801                 start_tick, end_tick = self.ParentWindow.GetRange()
   802                 rect = self.GetAxesBoundingBox()
   802                 rect = self.GetAxesBoundingBox()
   803                 
   803 
   804                 # Move graph along X coordinate
   804                 # Move graph along X coordinate
   805                 self.ParentWindow.SetCanvasPosition(
   805                 self.ParentWindow.SetCanvasPosition(
   806                     self.StartCursorTick + 
   806                     self.StartCursorTick +
   807                     (self.MouseStartPos.x - event.x) *
   807                     (self.MouseStartPos.x - event.x) *
   808                     (end_tick - start_tick) / rect.width)
   808                     (end_tick - start_tick) / rect.width)
   809     
   809 
   810     def OnCanvasScroll(self, event):
   810     def OnCanvasScroll(self, event):
   811         """
   811         """
   812         Function called when a wheel mouse is use in Viewer
   812         Function called when a wheel mouse is use in Viewer
   813         @param event: Mouse event
   813         @param event: Mouse event
   814         """
   814         """
   815         # Change X range of graphs if mouse is in canvas figure and ctrl is
   815         # Change X range of graphs if mouse is in canvas figure and ctrl is
   816         # pressed
   816         # pressed
   817         if event.inaxes is not None and event.guiEvent.ControlDown():
   817         if event.inaxes is not None and event.guiEvent.ControlDown():
   818             
   818 
   819             # Calculate position of fixed tick point according to graph type
   819             # Calculate position of fixed tick point according to graph type
   820             # and mouse position
   820             # and mouse position
   821             if self.GraphType == GRAPH_ORTHOGONAL:
   821             if self.GraphType == GRAPH_ORTHOGONAL:
   822                 start_tick, end_tick = self.ParentWindow.GetRange()
   822                 start_tick, end_tick = self.ParentWindow.GetRange()
   823                 tick = (start_tick + end_tick) / 2.
   823                 tick = (start_tick + end_tick) / 2.
   824             else:
   824             else:
   825                 tick = event.xdata
   825                 tick = event.xdata
   826             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
   826             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
   827             
   827 
   828             # Vetoing event to prevent parent panel to be scrolled
   828             # Vetoing event to prevent parent panel to be scrolled
   829             self.ParentWindow.VetoScrollEvent = True
   829             self.ParentWindow.VetoScrollEvent = True
   830     
   830 
   831     def OnLeftDClick(self, event):
   831     def OnLeftDClick(self, event):
   832         """
   832         """
   833         Function called when a left mouse button is double clicked
   833         Function called when a left mouse button is double clicked
   834         @param event: Mouse event
   834         @param event: Mouse event
   835         """
   835         """
   839         if rect.InsideXY(pos.x, pos.y):
   839         if rect.InsideXY(pos.x, pos.y):
   840             # Reset Cursor tick to value before starting clicking
   840             # Reset Cursor tick to value before starting clicking
   841             self.ParentWindow.SetCursorTick(self.StartCursorTick)
   841             self.ParentWindow.SetCursorTick(self.StartCursorTick)
   842             # Toggle to text Viewer(s)
   842             # Toggle to text Viewer(s)
   843             self.ParentWindow.ToggleViewerType(self)
   843             self.ParentWindow.ToggleViewerType(self)
   844         
   844 
   845         else:
   845         else:
   846             event.Skip()
   846             event.Skip()
   847     
   847 
   848     # Cursor tick move for each arrow key
   848     # Cursor tick move for each arrow key
   849     KEY_CURSOR_INCREMENT = {
   849     KEY_CURSOR_INCREMENT = {
   850         wx.WXK_LEFT: -1,
   850         wx.WXK_LEFT: -1,
   851         wx.WXK_RIGHT: 1,
   851         wx.WXK_RIGHT: 1,
   852         wx.WXK_UP: 10,
   852         wx.WXK_UP: 10,
   853         wx.WXK_DOWN: -10}
   853         wx.WXK_DOWN: -10}
   854     
   854 
   855     def OnKeyDown(self, event):
   855     def OnKeyDown(self, event):
   856         """
   856         """
   857         Function called when key is pressed
   857         Function called when key is pressed
   858         @param event: wx.KeyEvent
   858         @param event: wx.KeyEvent
   859         """
   859         """
   861         if self.CursorTick is not None:
   861         if self.CursorTick is not None:
   862             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
   862             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
   863             if move is not None:
   863             if move is not None:
   864                 self.ParentWindow.MoveCursorTick(move)
   864                 self.ParentWindow.MoveCursorTick(move)
   865         event.Skip()
   865         event.Skip()
   866     
   866 
   867     def OnLeave(self, event):
   867     def OnLeave(self, event):
   868         """
   868         """
   869         Function called when mouse leave Viewer
   869         Function called when mouse leave Viewer
   870         @param event: wx.MouseEvent
   870         @param event: wx.MouseEvent
   871         """
   871         """
   874             self.SetHighlight(HIGHLIGHT_NONE)
   874             self.SetHighlight(HIGHLIGHT_NONE)
   875             self.SetCursor(wx.NullCursor)
   875             self.SetCursor(wx.NullCursor)
   876             DebugVariableViewer.OnLeave(self, event)
   876             DebugVariableViewer.OnLeave(self, event)
   877         else:
   877         else:
   878             event.Skip()
   878             event.Skip()
   879     
   879 
   880     def GetCanvasMinSize(self):
   880     def GetCanvasMinSize(self):
   881         """
   881         """
   882         Return the minimum size of Viewer so that all items label can be
   882         Return the minimum size of Viewer so that all items label can be
   883         displayed
   883         displayed
   884         @return: wx.Size containing Viewer minimum size
   884         @return: wx.Size containing Viewer minimum size
   885         """
   885         """
   886         # The minimum height take in account the height of all items, padding
   886         # The minimum height take in account the height of all items, padding
   887         # inside figure and border around figure
   887         # inside figure and border around figure
   888         return wx.Size(200, 
   888         return wx.Size(200,
   889             CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
   889             CANVAS_BORDER[0] + CANVAS_BORDER[1] +
   890             2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
   890             2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
   891     
   891 
   892     def SetCanvasHeight(self, height):
   892     def SetCanvasHeight(self, height):
   893         """
   893         """
   894         Set Viewer size checking that it respects Viewer minimum size
   894         Set Viewer size checking that it respects Viewer minimum size
   895         @param height: Viewer height
   895         @param height: Viewer height
   896         """
   896         """
   897         min_width, min_height = self.GetCanvasMinSize()
   897         min_width, min_height = self.GetCanvasMinSize()
   898         height = max(height, min_height)
   898         height = max(height, min_height)
   899         self.SetMinSize(wx.Size(min_width, height))
   899         self.SetMinSize(wx.Size(min_width, height))
   900         self.RefreshLabelsPosition(height)
   900         self.RefreshLabelsPosition(height)
   901         self.ParentWindow.RefreshGraphicsSizer()
   901         self.ParentWindow.RefreshGraphicsSizer()
   902         
   902 
   903     def GetAxesBoundingBox(self, parent_coordinate=False):
   903     def GetAxesBoundingBox(self, parent_coordinate=False):
   904         """
   904         """
   905         Return figure bounding box in wx coordinate
   905         Return figure bounding box in wx coordinate
   906         @param parent_coordinate: True if use parent coordinate (default False)
   906         @param parent_coordinate: True if use parent coordinate (default False)
   907         """
   907         """
   909         # figure comparing to wx panel
   909         # figure comparing to wx panel
   910         width, height = self.GetSize()
   910         width, height = self.GetSize()
   911         ax, ay, aw, ah = self.figure.gca().get_position().bounds
   911         ax, ay, aw, ah = self.figure.gca().get_position().bounds
   912         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
   912         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
   913                        aw * width + 2, ah * height + 1)
   913                        aw * width + 2, ah * height + 1)
   914         
   914 
   915         # If parent_coordinate, add Viewer position in parent
   915         # If parent_coordinate, add Viewer position in parent
   916         if parent_coordinate:
   916         if parent_coordinate:
   917             xw, yw = self.GetPosition()
   917             xw, yw = self.GetPosition()
   918             bbox.x += xw
   918             bbox.x += xw
   919             bbox.y += yw
   919             bbox.y += yw
   920         
   920 
   921         return bbox
   921         return bbox
   922     
   922 
   923     def RefreshHighlight(self, x, y):
   923     def RefreshHighlight(self, x, y):
   924         """
   924         """
   925         Refresh Viewer highlight according to mouse position
   925         Refresh Viewer highlight according to mouse position
   926         @param x: X coordinate of mouse pointer
   926         @param x: X coordinate of mouse pointer
   927         @param y: Y coordinate of mouse pointer
   927         @param y: Y coordinate of mouse pointer
   928         """
   928         """
   929         width, height = self.GetSize()
   929         width, height = self.GetSize()
   930         
   930 
   931         # Mouse is over Viewer figure and graph is not 3D
   931         # Mouse is over Viewer figure and graph is not 3D
   932         bbox = self.GetAxesBoundingBox()
   932         bbox = self.GetAxesBoundingBox()
   933         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
   933         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
   934             rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
   934             rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
   935             # Mouse is over Viewer left part of figure
   935             # Mouse is over Viewer left part of figure
   936             if rect.InsideXY(x, y):
   936             if rect.InsideXY(x, y):
   937                 self.SetHighlight(HIGHLIGHT_LEFT)
   937                 self.SetHighlight(HIGHLIGHT_LEFT)
   938             
   938 
   939             # Mouse is over Viewer right part of figure
   939             # Mouse is over Viewer right part of figure
   940             else:
   940             else:
   941                 self.SetHighlight(HIGHLIGHT_RIGHT)
   941                 self.SetHighlight(HIGHLIGHT_RIGHT)
   942         
   942 
   943         # Mouse is over upper part of Viewer
   943         # Mouse is over upper part of Viewer
   944         elif y < height / 2:
   944         elif y < height / 2:
   945             # Viewer is upper one in Debug Variable Panel, show highlight
   945             # Viewer is upper one in Debug Variable Panel, show highlight
   946             if self.ParentWindow.IsViewerFirst(self):
   946             if self.ParentWindow.IsViewerFirst(self):
   947                 self.SetHighlight(HIGHLIGHT_BEFORE)
   947                 self.SetHighlight(HIGHLIGHT_BEFORE)
   948             
   948 
   949             # Viewer is not the upper one, show highlight in previous one
   949             # Viewer is not the upper one, show highlight in previous one
   950             # It prevents highlight to move when mouse leave one Viewer to
   950             # It prevents highlight to move when mouse leave one Viewer to
   951             # another
   951             # another
   952             else:
   952             else:
   953                 self.SetHighlight(HIGHLIGHT_NONE)
   953                 self.SetHighlight(HIGHLIGHT_NONE)
   954                 self.ParentWindow.HighlightPreviousViewer(self)
   954                 self.ParentWindow.HighlightPreviousViewer(self)
   955         
   955 
   956         # Mouse is over lower part of Viewer
   956         # Mouse is over lower part of Viewer
   957         else:
   957         else:
   958             self.SetHighlight(HIGHLIGHT_AFTER)
   958             self.SetHighlight(HIGHLIGHT_AFTER)
   959     
   959 
   960     def OnAxesMotion(self, event):
   960     def OnAxesMotion(self, event):
   961         """
   961         """
   962         Function overriding default function called when mouse is dragged for
   962         Function overriding default function called when mouse is dragged for
   963         rotating graph preventing refresh to be called too quickly
   963         rotating graph preventing refresh to be called too quickly
   964         @param event: Mouse event
   964         @param event: Mouse event
   967             # Call default function at most 10 times per second
   967             # Call default function at most 10 times per second
   968             current_time = gettime()
   968             current_time = gettime()
   969             if current_time - self.LastMotionTime > REFRESH_PERIOD:
   969             if current_time - self.LastMotionTime > REFRESH_PERIOD:
   970                 self.LastMotionTime = current_time
   970                 self.LastMotionTime = current_time
   971                 Axes3D._on_move(self.Axes, event)
   971                 Axes3D._on_move(self.Axes, event)
   972     
   972 
   973     def GetAddTextFunction(self):
   973     def GetAddTextFunction(self):
   974         """
   974         """
   975         Return function for adding text in figure according to graph type
   975         Return function for adding text in figure according to graph type
   976         @return: Function adding text to figure
   976         @return: Function adding text to figure
   977         """
   977         """
   985     def SetAxesColor(self, color):
   985     def SetAxesColor(self, color):
   986         if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"):
   986         if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"):
   987             self.Axes.set_prop_cycle(cycler('color',color))
   987             self.Axes.set_prop_cycle(cycler('color',color))
   988         else:
   988         else:
   989             self.Axes.set_color_cycle(color)
   989             self.Axes.set_color_cycle(color)
   990         
   990 
   991     def ResetGraphics(self):
   991     def ResetGraphics(self):
   992         """
   992         """
   993         Reset figure and graphical elements displayed in it
   993         Reset figure and graphical elements displayed in it
   994         Called any time list of items or graph type change 
   994         Called any time list of items or graph type change
   995         """
   995         """
   996         # Clear figure from any axes defined
   996         # Clear figure from any axes defined
   997         self.Figure.clear()
   997         self.Figure.clear()
   998         
   998 
   999         # Add 3D projection if graph is in 3D
   999         # Add 3D projection if graph is in 3D
  1000         if self.Is3DCanvas():
  1000         if self.Is3DCanvas():
  1001             self.Axes = self.Figure.gca(projection='3d')
  1001             self.Axes = self.Figure.gca(projection='3d')
  1002             self.SetAxesColor(['b'])
  1002             self.SetAxesColor(['b'])
  1003             
  1003 
  1004             # Override function to prevent too much refresh when graph is 
  1004             # Override function to prevent too much refresh when graph is
  1005             # rotated
  1005             # rotated
  1006             self.LastMotionTime = gettime()
  1006             self.LastMotionTime = gettime()
  1007             setattr(self.Axes, "_on_move", self.OnAxesMotion)
  1007             setattr(self.Axes, "_on_move", self.OnAxesMotion)
  1008             
  1008 
  1009             # Init graph mouse event so that graph can be rotated
  1009             # Init graph mouse event so that graph can be rotated
  1010             self.Axes.mouse_init()
  1010             self.Axes.mouse_init()
  1011             
  1011 
  1012             # Set size of Z axis labels
  1012             # Set size of Z axis labels
  1013             self.Axes.tick_params(axis='z', labelsize='small')
  1013             self.Axes.tick_params(axis='z', labelsize='small')
  1014         
  1014 
  1015         else:
  1015         else:
  1016             self.Axes = self.Figure.gca()
  1016             self.Axes = self.Figure.gca()
  1017             self.SetAxesColor(COLOR_CYCLE)
  1017             self.SetAxesColor(COLOR_CYCLE)
  1018         
  1018 
  1019         # Set size of X and Y axis labels
  1019         # Set size of X and Y axis labels
  1020         self.Axes.tick_params(axis='x', labelsize='small')
  1020         self.Axes.tick_params(axis='x', labelsize='small')
  1021         self.Axes.tick_params(axis='y', labelsize='small')
  1021         self.Axes.tick_params(axis='y', labelsize='small')
  1022         
  1022 
  1023         # Init variables storing graphical elements added to figure
  1023         # Init variables storing graphical elements added to figure
  1024         self.Plots = []      # List of curves
  1024         self.Plots = []      # List of curves
  1025         self.VLine = None    # Vertical line for cursor
  1025         self.VLine = None    # Vertical line for cursor
  1026         self.HLine = None    # Horizontal line for cursor (only orthogonal 2D)
  1026         self.HLine = None    # Horizontal line for cursor (only orthogonal 2D)
  1027         self.AxesLabels = [] # List of items variable path text label
  1027         self.AxesLabels = [] # List of items variable path text label
  1028         self.Labels = []     # List of items text label
  1028         self.Labels = []     # List of items text label
  1029         
  1029 
  1030         # Get function to add a text in figure according to graph type 
  1030         # Get function to add a text in figure according to graph type
  1031         add_text_func = self.GetAddTextFunction()
  1031         add_text_func = self.GetAddTextFunction()
  1032         
  1032 
  1033         # Graph type is parallel or orthogonal in 3D
  1033         # Graph type is parallel or orthogonal in 3D
  1034         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
  1034         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
  1035             num_item = len(self.Items)
  1035             num_item = len(self.Items)
  1036             for idx in xrange(num_item):
  1036             for idx in xrange(num_item):
  1037                 
  1037 
  1038                 # Get color from color cycle (black if only one item)
  1038                 # Get color from color cycle (black if only one item)
  1039                 color = ('k' if num_item == 1
  1039                 color = ('k' if num_item == 1
  1040                              else COLOR_CYCLE[idx % len(COLOR_CYCLE)])
  1040                              else COLOR_CYCLE[idx % len(COLOR_CYCLE)])
  1041                 
  1041 
  1042                 # In 3D graph items variable label are not displayed as text
  1042                 # In 3D graph items variable label are not displayed as text
  1043                 # in figure, but as axis title
  1043                 # in figure, but as axis title
  1044                 if not self.Is3DCanvas():
  1044                 if not self.Is3DCanvas():
  1045                     # Items variable labels are in figure upper left corner
  1045                     # Items variable labels are in figure upper left corner
  1046                     self.AxesLabels.append(
  1046                     self.AxesLabels.append(
  1047                         add_text_func(size='small', color=color,
  1047                         add_text_func(size='small', color=color,
  1048                                       verticalalignment='top'))
  1048                                       verticalalignment='top'))
  1049                 
  1049 
  1050                 # Items variable labels are in figure lower right corner
  1050                 # Items variable labels are in figure lower right corner
  1051                 self.Labels.append(
  1051                 self.Labels.append(
  1052                     add_text_func(size='large', color=color, 
  1052                     add_text_func(size='large', color=color,
  1053                                   horizontalalignment='right'))
  1053                                   horizontalalignment='right'))
  1054         
  1054 
  1055         # Graph type is orthogonal in 2D
  1055         # Graph type is orthogonal in 2D
  1056         else:
  1056         else:
  1057             # X coordinate labels are in figure lower side
  1057             # X coordinate labels are in figure lower side
  1058             self.AxesLabels.append(add_text_func(size='small'))
  1058             self.AxesLabels.append(add_text_func(size='small'))
  1059             self.Labels.append(
  1059             self.Labels.append(
  1060                 add_text_func(size='large',
  1060                 add_text_func(size='large',
  1061                               horizontalalignment='right'))
  1061                               horizontalalignment='right'))
  1062             
  1062 
  1063             # Y coordinate labels are vertical and in figure left side
  1063             # Y coordinate labels are vertical and in figure left side
  1064             self.AxesLabels.append(
  1064             self.AxesLabels.append(
  1065                 add_text_func(size='small', rotation='vertical',
  1065                 add_text_func(size='small', rotation='vertical',
  1066                               verticalalignment='bottom'))
  1066                               verticalalignment='bottom'))
  1067             self.Labels.append(
  1067             self.Labels.append(
  1068                 add_text_func(size='large', rotation='vertical',
  1068                 add_text_func(size='large', rotation='vertical',
  1069                               verticalalignment='top'))
  1069                               verticalalignment='top'))
  1070        
  1070 
  1071         # Refresh position of labels according to Viewer size
  1071         # Refresh position of labels according to Viewer size
  1072         width, height = self.GetSize()
  1072         width, height = self.GetSize()
  1073         self.RefreshLabelsPosition(height)
  1073         self.RefreshLabelsPosition(height)
  1074     
  1074 
  1075     def RefreshLabelsPosition(self, height):
  1075     def RefreshLabelsPosition(self, height):
  1076         """
  1076         """
  1077         Function called when mouse leave Viewer
  1077         Function called when mouse leave Viewer
  1078         @param event: wx.MouseEvent
  1078         @param event: wx.MouseEvent
  1079         """
  1079         """
  1085         # matplotlib coordinate
  1085         # matplotlib coordinate
  1086         canvas_ratio = 1. / height # Divide by canvas height in pixel
  1086         canvas_ratio = 1. / height # Divide by canvas height in pixel
  1087         graph_ratio = 1. / (
  1087         graph_ratio = 1. / (
  1088             (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio)
  1088             (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio)
  1089              * height)             # Divide by figure height in pixel
  1089              * height)             # Divide by figure height in pixel
  1090         
  1090 
  1091         # Update position of figure (keeping up and bottom border the same
  1091         # Update position of figure (keeping up and bottom border the same
  1092         # size)
  1092         # size)
  1093         self.Figure.subplotpars.update(
  1093         self.Figure.subplotpars.update(
  1094             top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
  1094             top= 1.0 - CANVAS_BORDER[1] * canvas_ratio,
  1095             bottom= CANVAS_BORDER[0] * canvas_ratio)
  1095             bottom= CANVAS_BORDER[0] * canvas_ratio)
  1096         
  1096 
  1097         # Update position of items labels
  1097         # Update position of items labels
  1098         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
  1098         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
  1099             num_item = len(self.Items)
  1099             num_item = len(self.Items)
  1100             for idx in xrange(num_item):
  1100             for idx in xrange(num_item):
  1101                 
  1101 
  1102                 # In 3D graph items variable label are not displayed
  1102                 # In 3D graph items variable label are not displayed
  1103                 if not self.Is3DCanvas():
  1103                 if not self.Is3DCanvas():
  1104                     # Items variable labels are in figure upper left corner
  1104                     # Items variable labels are in figure upper left corner
  1105                     self.AxesLabels[idx].set_position(
  1105                     self.AxesLabels[idx].set_position(
  1106                         (0.05, 
  1106                         (0.05,
  1107                          1.0 - (CANVAS_PADDING + 
  1107                          1.0 - (CANVAS_PADDING +
  1108                                 AXES_LABEL_HEIGHT * idx) * graph_ratio))
  1108                                 AXES_LABEL_HEIGHT * idx) * graph_ratio))
  1109                 
  1109 
  1110                 # Items variable labels are in figure lower right corner
  1110                 # Items variable labels are in figure lower right corner
  1111                 self.Labels[idx].set_position(
  1111                 self.Labels[idx].set_position(
  1112                     (0.95, 
  1112                     (0.95,
  1113                      CANVAS_PADDING * graph_ratio + 
  1113                      CANVAS_PADDING * graph_ratio +
  1114                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
  1114                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
  1115         else:
  1115         else:
  1116             # X coordinate labels are in figure lower side
  1116             # X coordinate labels are in figure lower side
  1117             self.AxesLabels[0].set_position(
  1117             self.AxesLabels[0].set_position(
  1118                     (0.1, CANVAS_PADDING * graph_ratio))
  1118                     (0.1, CANVAS_PADDING * graph_ratio))
  1119             self.Labels[0].set_position(
  1119             self.Labels[0].set_position(
  1120                     (0.95, CANVAS_PADDING * graph_ratio))
  1120                     (0.95, CANVAS_PADDING * graph_ratio))
  1121             
  1121 
  1122             # Y coordinate labels are vertical and in figure left side
  1122             # Y coordinate labels are vertical and in figure left side
  1123             self.AxesLabels[1].set_position(
  1123             self.AxesLabels[1].set_position(
  1124                     (0.05, 2 * CANVAS_PADDING * graph_ratio))
  1124                     (0.05, 2 * CANVAS_PADDING * graph_ratio))
  1125             self.Labels[1].set_position(
  1125             self.Labels[1].set_position(
  1126                     (0.05, 1.0 - CANVAS_PADDING * graph_ratio))
  1126                     (0.05, 1.0 - CANVAS_PADDING * graph_ratio))
  1127         
  1127 
  1128         # Update subplots
  1128         # Update subplots
  1129         self.Figure.subplots_adjust()
  1129         self.Figure.subplots_adjust()
  1130     
  1130 
  1131     def RefreshViewer(self, refresh_graphics=True):
  1131     def RefreshViewer(self, refresh_graphics=True):
  1132         """
  1132         """
  1133         Function called to refresh displayed by matplotlib canvas
  1133         Function called to refresh displayed by matplotlib canvas
  1134         @param refresh_graphics: Flag indicating that graphs have to be
  1134         @param refresh_graphics: Flag indicating that graphs have to be
  1135         refreshed (False: only label values have to be refreshed)
  1135         refreshed (False: only label values have to be refreshed)
  1136         """
  1136         """
  1137         # Refresh graphs if needed
  1137         # Refresh graphs if needed
  1138         if refresh_graphics:
  1138         if refresh_graphics:
  1139             # Get tick range of values to display
  1139             # Get tick range of values to display
  1140             start_tick, end_tick = self.ParentWindow.GetRange()
  1140             start_tick, end_tick = self.ParentWindow.GetRange()
  1141             
  1141 
  1142             # Graph is parallel
  1142             # Graph is parallel
  1143             if self.GraphType == GRAPH_PARALLEL:    
  1143             if self.GraphType == GRAPH_PARALLEL:
  1144                 # Init list of data range for each variable displayed
  1144                 # Init list of data range for each variable displayed
  1145                 ranges = []
  1145                 ranges = []
  1146                 
  1146 
  1147                 # Get data and range for each variable displayed
  1147                 # Get data and range for each variable displayed
  1148                 for idx, item in enumerate(self.Items):
  1148                 for idx, item in enumerate(self.Items):
  1149                     data, min_value, max_value = item.GetDataAndValueRange(
  1149                     data, min_value, max_value = item.GetDataAndValueRange(
  1150                                 start_tick, end_tick, not self.ZoomFit)
  1150                                 start_tick, end_tick, not self.ZoomFit)
  1151                     
  1151 
  1152                     # Check that data is not empty
  1152                     # Check that data is not empty
  1153                     if data is not None:
  1153                     if data is not None:
  1154                         # Add variable range to list of variable data range
  1154                         # Add variable range to list of variable data range
  1155                         ranges.append((min_value, max_value))
  1155                         ranges.append((min_value, max_value))
  1156                         
  1156 
  1157                         # Add plot to canvas if not yet created
  1157                         # Add plot to canvas if not yet created
  1158                         if len(self.Plots) <= idx:
  1158                         if len(self.Plots) <= idx:
  1159                             self.Plots.append(
  1159                             self.Plots.append(
  1160                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
  1160                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
  1161                         
  1161 
  1162                         # Set data to already created plot in canvas
  1162                         # Set data to already created plot in canvas
  1163                         else:
  1163                         else:
  1164                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
  1164                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
  1165                 
  1165 
  1166                 # Get X and Y axis ranges
  1166                 # Get X and Y axis ranges
  1167                 x_min, x_max = start_tick, end_tick
  1167                 x_min, x_max = start_tick, end_tick
  1168                 y_min, y_max = merge_ranges(ranges)
  1168                 y_min, y_max = merge_ranges(ranges)
  1169                 
  1169 
  1170                 # Display cursor in canvas if a cursor tick is defined and it is
  1170                 # Display cursor in canvas if a cursor tick is defined and it is
  1171                 # include in values tick range
  1171                 # include in values tick range
  1172                 if (self.CursorTick is not None and 
  1172                 if (self.CursorTick is not None and
  1173                     start_tick <= self.CursorTick <= end_tick):
  1173                     start_tick <= self.CursorTick <= end_tick):
  1174                     
  1174 
  1175                     # Define a vertical line to display cursor position if no
  1175                     # Define a vertical line to display cursor position if no
  1176                     # line is already defined
  1176                     # line is already defined
  1177                     if self.VLine is None:
  1177                     if self.VLine is None:
  1178                         self.VLine = self.Axes.axvline(self.CursorTick, 
  1178                         self.VLine = self.Axes.axvline(self.CursorTick,
  1179                                                        color=CURSOR_COLOR)
  1179                                                        color=CURSOR_COLOR)
  1180                     
  1180 
  1181                     # Set value of vertical line if already defined
  1181                     # Set value of vertical line if already defined
  1182                     else:
  1182                     else:
  1183                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
  1183                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
  1184                     self.VLine.set_visible(True)
  1184                     self.VLine.set_visible(True)
  1185                 
  1185 
  1186                 # Hide vertical line if cursor tick is not defined or reset
  1186                 # Hide vertical line if cursor tick is not defined or reset
  1187                 elif self.VLine is not None:
  1187                 elif self.VLine is not None:
  1188                     self.VLine.set_visible(False)
  1188                     self.VLine.set_visible(False)
  1189             
  1189 
  1190             # Graph is orthogonal
  1190             # Graph is orthogonal
  1191             else:
  1191             else:
  1192                 # Update tick range, removing ticks that don't have a value for
  1192                 # Update tick range, removing ticks that don't have a value for
  1193                 # each variable
  1193                 # each variable
  1194                 start_tick = max(start_tick, self.GetItemsMinCommonTick())
  1194                 start_tick = max(start_tick, self.GetItemsMinCommonTick())
  1195                 end_tick = max(end_tick, start_tick)
  1195                 end_tick = max(end_tick, start_tick)
  1196                 items = self.ItemsDict.values()
  1196                 items = self.ItemsDict.values()
  1197                 
  1197 
  1198                 # Get data and range for first variable (X coordinate)
  1198                 # Get data and range for first variable (X coordinate)
  1199                 x_data, x_min, x_max = items[0].GetDataAndValueRange(
  1199                 x_data, x_min, x_max = items[0].GetDataAndValueRange(
  1200                                         start_tick, end_tick, not self.ZoomFit)
  1200                                         start_tick, end_tick, not self.ZoomFit)
  1201                 # Get data and range for second variable (Y coordinate)
  1201                 # Get data and range for second variable (Y coordinate)
  1202                 y_data, y_min, y_max = items[1].GetDataAndValueRange(
  1202                 y_data, y_min, y_max = items[1].GetDataAndValueRange(
  1203                                         start_tick, end_tick, not self.ZoomFit)
  1203                                         start_tick, end_tick, not self.ZoomFit)
  1204                 
  1204 
  1205                 # Normalize X and Y coordinates value range
  1205                 # Normalize X and Y coordinates value range
  1206                 x_min, x_max = merge_ranges([(x_min, x_max)])
  1206                 x_min, x_max = merge_ranges([(x_min, x_max)])
  1207                 y_min, y_max = merge_ranges([(y_min, y_max)])
  1207                 y_min, y_max = merge_ranges([(y_min, y_max)])
  1208                 
  1208 
  1209                 # Get X and Y coordinates for cursor if cursor tick is defined 
  1209                 # Get X and Y coordinates for cursor if cursor tick is defined
  1210                 if self.CursorTick is not None:
  1210                 if self.CursorTick is not None:
  1211                     x_cursor, x_forced = items[0].GetValue(
  1211                     x_cursor, x_forced = items[0].GetValue(
  1212                                             self.CursorTick, raw=True)
  1212                                             self.CursorTick, raw=True)
  1213                     y_cursor, y_forced = items[1].GetValue(
  1213                     y_cursor, y_forced = items[1].GetValue(
  1214                                             self.CursorTick, raw=True)
  1214                                             self.CursorTick, raw=True)
  1215                 
  1215 
  1216                 # Get common data length so that each value has an x and y
  1216                 # Get common data length so that each value has an x and y
  1217                 # coordinate
  1217                 # coordinate
  1218                 length = (min(len(x_data), len(y_data))
  1218                 length = (min(len(x_data), len(y_data))
  1219                           if x_data is not None and y_data is not None
  1219                           if x_data is not None and y_data is not None
  1220                           else 0)
  1220                           else 0)
  1221                 
  1221 
  1222                 # Graph is orthogonal 2D 
  1222                 # Graph is orthogonal 2D
  1223                 if len(self.Items) < 3:
  1223                 if len(self.Items) < 3:
  1224                     
  1224 
  1225                     # Check that x and y data are not empty
  1225                     # Check that x and y data are not empty
  1226                     if x_data is not None and y_data is not None:
  1226                     if x_data is not None and y_data is not None:
  1227                         
  1227 
  1228                         # Add plot to canvas if not yet created
  1228                         # Add plot to canvas if not yet created
  1229                         if len(self.Plots) == 0:
  1229                         if len(self.Plots) == 0:
  1230                             self.Plots.append(
  1230                             self.Plots.append(
  1231                                 self.Axes.plot(x_data[:, 1][:length], 
  1231                                 self.Axes.plot(x_data[:, 1][:length],
  1232                                                y_data[:, 1][:length])[0])
  1232                                                y_data[:, 1][:length])[0])
  1233                         
  1233 
  1234                         # Set data to already created plot in canvas
  1234                         # Set data to already created plot in canvas
  1235                         else:
  1235                         else:
  1236                             self.Plots[0].set_data(
  1236                             self.Plots[0].set_data(
  1237                                 x_data[:, 1][:length], 
  1237                                 x_data[:, 1][:length],
  1238                                 y_data[:, 1][:length])
  1238                                 y_data[:, 1][:length])
  1239                     
  1239 
  1240                     # Display cursor in canvas if a cursor tick is defined and it is
  1240                     # Display cursor in canvas if a cursor tick is defined and it is
  1241                     # include in values tick range
  1241                     # include in values tick range
  1242                     if (self.CursorTick is not None and 
  1242                     if (self.CursorTick is not None and
  1243                         start_tick <= self.CursorTick <= end_tick):
  1243                         start_tick <= self.CursorTick <= end_tick):
  1244                         
  1244 
  1245                         # Define a vertical line to display cursor x coordinate
  1245                         # Define a vertical line to display cursor x coordinate
  1246                         # if no line is already defined
  1246                         # if no line is already defined
  1247                         if self.VLine is None:
  1247                         if self.VLine is None:
  1248                             self.VLine = self.Axes.axvline(x_cursor, 
  1248                             self.VLine = self.Axes.axvline(x_cursor,
  1249                                                            color=CURSOR_COLOR)
  1249                                                            color=CURSOR_COLOR)
  1250                         # Set value of vertical line if already defined
  1250                         # Set value of vertical line if already defined
  1251                         else:
  1251                         else:
  1252                             self.VLine.set_xdata((x_cursor, x_cursor))
  1252                             self.VLine.set_xdata((x_cursor, x_cursor))
  1253                         
  1253 
  1254                         
  1254 
  1255                         # Define a horizontal line to display cursor y
  1255                         # Define a horizontal line to display cursor y
  1256                         # coordinate if no line is already defined
  1256                         # coordinate if no line is already defined
  1257                         if self.HLine is None:
  1257                         if self.HLine is None:
  1258                             self.HLine = self.Axes.axhline(y_cursor, 
  1258                             self.HLine = self.Axes.axhline(y_cursor,
  1259                                                            color=CURSOR_COLOR)
  1259                                                            color=CURSOR_COLOR)
  1260                         # Set value of horizontal line if already defined
  1260                         # Set value of horizontal line if already defined
  1261                         else:
  1261                         else:
  1262                             self.HLine.set_ydata((y_cursor, y_cursor))
  1262                             self.HLine.set_ydata((y_cursor, y_cursor))
  1263                         
  1263 
  1264                         self.VLine.set_visible(True)
  1264                         self.VLine.set_visible(True)
  1265                         self.HLine.set_visible(True)
  1265                         self.HLine.set_visible(True)
  1266                     
  1266 
  1267                     # Hide vertical and horizontal line if cursor tick is not
  1267                     # Hide vertical and horizontal line if cursor tick is not
  1268                     # defined or reset
  1268                     # defined or reset
  1269                     else:
  1269                     else:
  1270                         if self.VLine is not None:
  1270                         if self.VLine is not None:
  1271                             self.VLine.set_visible(False)
  1271                             self.VLine.set_visible(False)
  1272                         if self.HLine is not None:
  1272                         if self.HLine is not None:
  1273                             self.HLine.set_visible(False)
  1273                             self.HLine.set_visible(False)
  1274                 
  1274 
  1275                 # Graph is orthogonal 3D
  1275                 # Graph is orthogonal 3D
  1276                 else:
  1276                 else:
  1277                     # Remove all plots already defined in 3D canvas
  1277                     # Remove all plots already defined in 3D canvas
  1278                     while len(self.Axes.lines) > 0:
  1278                     while len(self.Axes.lines) > 0:
  1279                         self.Axes.lines.pop()
  1279                         self.Axes.lines.pop()
  1280                     
  1280 
  1281                     # Get data and range for third variable (Z coordinate)
  1281                     # Get data and range for third variable (Z coordinate)
  1282                     z_data, z_min, z_max = items[2].GetDataAndValueRange(
  1282                     z_data, z_min, z_max = items[2].GetDataAndValueRange(
  1283                                     start_tick, end_tick, not self.ZoomFit)
  1283                                     start_tick, end_tick, not self.ZoomFit)
  1284                     
  1284 
  1285                     # Normalize Z coordinate value range
  1285                     # Normalize Z coordinate value range
  1286                     z_min, z_max = merge_ranges([(z_min, z_max)])
  1286                     z_min, z_max = merge_ranges([(z_min, z_max)])
  1287                     
  1287 
  1288                     # Check that x, y and z data are not empty
  1288                     # Check that x, y and z data are not empty
  1289                     if (x_data is not None and y_data is not None and 
  1289                     if (x_data is not None and y_data is not None and
  1290                         z_data is not None):
  1290                         z_data is not None):
  1291                         
  1291 
  1292                         # Get common data length so that each value has an x, y
  1292                         # Get common data length so that each value has an x, y
  1293                         # and z coordinate
  1293                         # and z coordinate
  1294                         length = min(length, len(z_data))
  1294                         length = min(length, len(z_data))
  1295                         
  1295 
  1296                         # Add plot to canvas
  1296                         # Add plot to canvas
  1297                         self.Axes.plot(x_data[:, 1][:length],
  1297                         self.Axes.plot(x_data[:, 1][:length],
  1298                                        y_data[:, 1][:length],
  1298                                        y_data[:, 1][:length],
  1299                                        zs = z_data[:, 1][:length])
  1299                                        zs = z_data[:, 1][:length])
  1300                     
  1300 
  1301                     # Display cursor in canvas if a cursor tick is defined and
  1301                     # Display cursor in canvas if a cursor tick is defined and
  1302                     # it is include in values tick range
  1302                     # it is include in values tick range
  1303                     if (self.CursorTick is not None and 
  1303                     if (self.CursorTick is not None and
  1304                         start_tick <= self.CursorTick <= end_tick):
  1304                         start_tick <= self.CursorTick <= end_tick):
  1305                         
  1305 
  1306                         # Get Z coordinate for cursor
  1306                         # Get Z coordinate for cursor
  1307                         z_cursor, z_forced = items[2].GetValue(
  1307                         z_cursor, z_forced = items[2].GetValue(
  1308                                                 self.CursorTick, raw=True)
  1308                                                 self.CursorTick, raw=True)
  1309                         
  1309 
  1310                         # Add 3 lines parallel to x, y and z axis to display
  1310                         # Add 3 lines parallel to x, y and z axis to display
  1311                         # cursor position in 3D
  1311                         # cursor position in 3D
  1312                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
  1312                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
  1313                                        {"ys": numpy.array([y_min, y_max])},
  1313                                        {"ys": numpy.array([y_min, y_max])},
  1314                                        {"zs": numpy.array([z_min, z_max])}]:
  1314                                        {"zs": numpy.array([z_min, z_max])}]:
  1317                                     ("ys", numpy.array([y_cursor, y_cursor])),
  1317                                     ("ys", numpy.array([y_cursor, y_cursor])),
  1318                                     ("zs", numpy.array([z_cursor, z_cursor]))]:
  1318                                     ("zs", numpy.array([z_cursor, z_cursor]))]:
  1319                                 kwargs.setdefault(param, value)
  1319                                 kwargs.setdefault(param, value)
  1320                             kwargs["color"] = CURSOR_COLOR
  1320                             kwargs["color"] = CURSOR_COLOR
  1321                             self.Axes.plot(**kwargs)
  1321                             self.Axes.plot(**kwargs)
  1322                     
  1322 
  1323                     # Set Z axis limits
  1323                     # Set Z axis limits
  1324                     self.Axes.set_zlim(z_min, z_max)
  1324                     self.Axes.set_zlim(z_min, z_max)
  1325             
  1325 
  1326             # Set X and Y axis limits
  1326             # Set X and Y axis limits
  1327             self.Axes.set_xlim(x_min, x_max)
  1327             self.Axes.set_xlim(x_min, x_max)
  1328             self.Axes.set_ylim(y_min, y_max)
  1328             self.Axes.set_ylim(y_min, y_max)
  1329         
  1329 
  1330         # Get value and forced flag for each variable displayed in graph
  1330         # Get value and forced flag for each variable displayed in graph
  1331         # If cursor tick is not defined get value and flag of last received
  1331         # If cursor tick is not defined get value and flag of last received
  1332         # or get value and flag of variable at cursor tick
  1332         # or get value and flag of variable at cursor tick
  1333         values, forced = apply(zip, [
  1333         values, forced = apply(zip, [
  1334                 (item.GetValue(self.CursorTick)
  1334                 (item.GetValue(self.CursorTick)
  1335                  if self.CursorTick is not None
  1335                  if self.CursorTick is not None
  1336                  else (item.GetValue(), item.IsForced()))
  1336                  else (item.GetValue(), item.IsForced()))
  1337                 for item in self.Items])
  1337                 for item in self.Items])
  1338         
  1338 
  1339         # Get path of each variable displayed simplified using panel variable
  1339         # Get path of each variable displayed simplified using panel variable
  1340         # name mask
  1340         # name mask
  1341         labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask()) 
  1341         labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask())
  1342                   for item in self.Items]
  1342                   for item in self.Items]
  1343         
  1343 
  1344         # Get style for each variable according to 
  1344         # Get style for each variable according to
  1345         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
  1345         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
  1346         
  1346 
  1347         # Graph is orthogonal 3D, set variables path as 3D axis label
  1347         # Graph is orthogonal 3D, set variables path as 3D axis label
  1348         if self.Is3DCanvas():
  1348         if self.Is3DCanvas():
  1349             for idx, label_func in enumerate([self.Axes.set_xlabel, 
  1349             for idx, label_func in enumerate([self.Axes.set_xlabel,
  1350                                               self.Axes.set_ylabel,
  1350                                               self.Axes.set_ylabel,
  1351                                               self.Axes.set_zlabel]):
  1351                                               self.Axes.set_zlabel]):
  1352                 label_func(labels[idx], fontdict={'size': 'small',
  1352                 label_func(labels[idx], fontdict={'size': 'small',
  1353                                                   'color': COLOR_CYCLE[idx]})
  1353                                                   'color': COLOR_CYCLE[idx]})
  1354         
  1354 
  1355         # Graph is not orthogonal 3D, set variables path in axes labels
  1355         # Graph is not orthogonal 3D, set variables path in axes labels
  1356         else:
  1356         else:
  1357             for label, text in zip(self.AxesLabels, labels):
  1357             for label, text in zip(self.AxesLabels, labels):
  1358                 label.set_text(text)
  1358                 label.set_text(text)
  1359         
  1359 
  1360         # Set value label text and style according to value and forced flag for
  1360         # Set value label text and style according to value and forced flag for
  1361         # each variable displayed
  1361         # each variable displayed
  1362         for label, value, style in zip(self.Labels, values, styles):
  1362         for label, value, style in zip(self.Labels, values, styles):
  1363             label.set_text(value)
  1363             label.set_text(value)
  1364             label.set_style(style)
  1364             label.set_style(style)
  1365         
  1365 
  1366         # Refresh figure
  1366         # Refresh figure
  1367         self.draw()
  1367         self.draw()
  1368 
  1368 
  1369     def draw(self, drawDC=None):
  1369     def draw(self, drawDC=None):
  1370         """
  1370         """
  1371         Render the figure.
  1371         Render the figure.
  1372         """
  1372         """
  1373         # Render figure using agg
  1373         # Render figure using agg
  1374         FigureCanvasAgg.draw(self)
  1374         FigureCanvasAgg.draw(self)
  1375         
  1375 
  1376         # Get bitmap of figure rendered
  1376         # Get bitmap of figure rendered
  1377         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
  1377         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
  1378         if wx.VERSION < (3, 0, 0):        
  1378         if wx.VERSION < (3, 0, 0):
  1379             self.bitmap.UseAlpha()
  1379             self.bitmap.UseAlpha()
  1380         
  1380 
  1381         # Create DC for rendering graphics in bitmap
  1381         # Create DC for rendering graphics in bitmap
  1382         destDC = wx.MemoryDC()
  1382         destDC = wx.MemoryDC()
  1383         destDC.SelectObject(self.bitmap)
  1383         destDC.SelectObject(self.bitmap)
  1384         
  1384 
  1385         # Get Graphics Context for DC, for anti-aliased and transparent
  1385         # Get Graphics Context for DC, for anti-aliased and transparent
  1386         # rendering
  1386         # rendering
  1387         destGC = wx.GCDC(destDC)
  1387         destGC = wx.GCDC(destDC)
  1388         
  1388 
  1389         destGC.BeginDrawing()
  1389         destGC.BeginDrawing()
  1390         
  1390 
  1391         # Get canvas size and figure bounding box in canvas
  1391         # Get canvas size and figure bounding box in canvas
  1392         width, height = self.GetSize()
  1392         width, height = self.GetSize()
  1393         bbox = self.GetAxesBoundingBox()
  1393         bbox = self.GetAxesBoundingBox()
  1394         
  1394 
  1395         # If highlight to display is resize, draw thick grey line at bottom
  1395         # If highlight to display is resize, draw thick grey line at bottom
  1396         # side of canvas 
  1396         # side of canvas
  1397         if self.Highlight == HIGHLIGHT_RESIZE:
  1397         if self.Highlight == HIGHLIGHT_RESIZE:
  1398             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
  1398             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
  1399             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
  1399             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
  1400             destGC.DrawRectangle(0, height - 5, width, 5)
  1400             destGC.DrawRectangle(0, height - 5, width, 5)
  1401         
  1401 
  1402         # If highlight to display is merging graph, draw 50% transparent blue
  1402         # If highlight to display is merging graph, draw 50% transparent blue
  1403         # rectangle on left or right part of figure depending on highlight type
  1403         # rectangle on left or right part of figure depending on highlight type
  1404         elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
  1404         elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
  1405             destGC.SetPen(HIGHLIGHT_DROP_PEN)
  1405             destGC.SetPen(HIGHLIGHT_DROP_PEN)
  1406             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
  1406             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
  1407             
  1407 
  1408             x_offset = (bbox.width / 2 
  1408             x_offset = (bbox.width / 2
  1409                         if self.Highlight == HIGHLIGHT_RIGHT
  1409                         if self.Highlight == HIGHLIGHT_RIGHT
  1410                         else 0)
  1410                         else 0)
  1411             destGC.DrawRectangle(bbox.x + x_offset, bbox.y, 
  1411             destGC.DrawRectangle(bbox.x + x_offset, bbox.y,
  1412                                  bbox.width / 2, bbox.height)
  1412                                  bbox.width / 2, bbox.height)
  1413         
  1413 
  1414         # Draw other Viewer common elements
  1414         # Draw other Viewer common elements
  1415         self.DrawCommonElements(destGC, self.GetButtons())
  1415         self.DrawCommonElements(destGC, self.GetButtons())
  1416         
  1416 
  1417         destGC.EndDrawing()
  1417         destGC.EndDrawing()
  1418         
  1418 
  1419         self._isDrawn = True
  1419         self._isDrawn = True
  1420         self.gui_repaint(drawDC=drawDC)
  1420         self.gui_repaint(drawDC=drawDC)
  1421     
       
  1422