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