controls/DebugVariablePanel/DebugVariableGraphicViewer.py
changeset 1730 64d8f52bc8c8
parent 1571 486f94a8032c
child 1734 750eeb7230a1
--- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Fri Aug 11 15:18:19 2017 +0300
+++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py	Mon Aug 14 19:13:01 2017 +0300
@@ -83,23 +83,23 @@
             min_value = range_min
         elif range_min is not None:
             min_value = min(min_value, range_min)
-        
+
         # Update maximal range value
         if max_value is None:
             max_value = range_max
         elif range_min is not None:
             max_value = max(max_value, range_max)
-    
+
     # Calculate range center and width if at least one valid range is defined
     if min_value is not None and max_value is not None:
         center = (min_value + max_value) / 2.
         range_size = max(1.0, max_value - min_value)
-    
+
     # Set default center and with if no valid range is defined
     else:
         center = 0.5
         range_size = 1.0
-    
+
     # Return range expended from 10 %
     return center - range_size * 0.55, center + range_size * 0.55
 
@@ -113,7 +113,7 @@
 """
 
 class DebugVariableGraphicDropTarget(wx.TextDropTarget):
-    
+
     def __init__(self, parent, window):
         """
         Constructor
@@ -123,7 +123,7 @@
         wx.TextDropTarget.__init__(self)
         self.ParentControl = parent
         self.ParentWindow = window
-        
+
     def __del__(self):
         """
         Destructor
@@ -132,7 +132,7 @@
         # Panel
         self.ParentControl = None
         self.ParentWindow = None
-        
+
     def OnDragOver(self, x, y, d):
         """
         Function called when mouse is dragged over Drop Target
@@ -142,9 +142,9 @@
         """
         # Signal parent that mouse is dragged over
         self.ParentControl.OnMouseDragging(x, y)
-        
+
         return wx.TextDropTarget.OnDragOver(self, x, y, d)
-        
+
     def OnDropText(self, x, y, data):
         """
         Function called when mouse is released in Drop Target
@@ -154,9 +154,9 @@
         """
         # Signal Debug Variable Panel to reset highlight
         self.ParentWindow.ResetHighlight()
-        
+
         message = None
-        
+
         # Check that data is valid regarding DebugVariablePanel
         try:
             values = eval(data)
@@ -165,54 +165,54 @@
         except:
             message = _("Invalid value \"%s\" for debug variable")%data
             values = None
-        
+
         # Display message if data is invalid
         if message is not None:
             wx.CallAfter(self.ShowMessage, message)
-        
+
         # Data contain a reference to a variable to debug
         elif values[1] == "debug":
             target_idx = self.ParentControl.GetIndex()
-            
+
             # If mouse is dropped in graph canvas bounding box and graph is
             # not 3D canvas, graphs will be merged
             rect = self.ParentControl.GetAxesBoundingBox()
             if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
                 # Default merge type is parallel
                 merge_type = GRAPH_PARALLEL
-                
+
                 # If mouse is dropped in left part of graph canvas, graph
                 # wall be merged orthogonally
-                merge_rect = wx.Rect(rect.x, rect.y, 
+                merge_rect = wx.Rect(rect.x, rect.y,
                                      rect.width / 2., rect.height)
                 if merge_rect.InsideXY(x, y):
                     merge_type = GRAPH_ORTHOGONAL
-                
+
                 # Merge graphs
-                wx.CallAfter(self.ParentWindow.MergeGraphs, 
-                             values[0], target_idx, 
+                wx.CallAfter(self.ParentWindow.MergeGraphs,
+                             values[0], target_idx,
                              merge_type, force=True)
-                
+
             else:
                 width, height = self.ParentControl.GetSize()
-                
+
                 # Get Before which Viewer the variable has to be moved or added
                 # according to the position of mouse in Viewer.
                 if y > height / 2:
                     target_idx += 1
-                
+
                 # Drag'n Drop is an internal is an internal move inside Debug
-                # Variable Panel 
+                # Variable Panel
                 if len(values) > 2 and values[2] == "move":
-                    self.ParentWindow.MoveValue(values[0], 
+                    self.ParentWindow.MoveValue(values[0],
                                                 target_idx)
-                
+
                 # Drag'n Drop was initiated by another control of Beremiz
                 else:
-                    self.ParentWindow.InsertValue(values[0], 
-                                                  target_idx, 
+                    self.ParentWindow.InsertValue(values[0],
+                                                  target_idx,
                                                   force=True)
-    
+
     def OnLeave(self):
         """
         Function called when mouse is leave Drop Target
@@ -220,15 +220,15 @@
         # Signal Debug Variable Panel to reset highlight
         self.ParentWindow.ResetHighlight()
         return wx.TextDropTarget.OnLeave(self)
-    
+
     def ShowMessage(self, message):
         """
         Show error message in Error Dialog
         @param message: Error message to display
         """
-        dialog = wx.MessageDialog(self.ParentWindow, 
-                                  message, 
-                                  _("Error"), 
+        dialog = wx.MessageDialog(self.ParentWindow,
+                                  message,
+                                  _("Error"),
                                   wx.OK|wx.ICON_ERROR)
         dialog.ShowModal()
         dialog.Destroy()
@@ -243,7 +243,7 @@
 """
 
 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
-    
+
     def __init__(self, parent, window, items, graph_type):
         """
         Constructor
@@ -253,36 +253,36 @@
         @param graph_type: Graph display type (Parallel or orthogonal)
         """
         DebugVariableViewer.__init__(self, window, items)
-        
+
         self.GraphType = graph_type        # Graph type display
         self.CursorTick = None             # Tick of the graph cursor
-        
+
         # Mouse position when start dragging
         self.MouseStartPos = None
         # Tick when moving tick start
         self.StartCursorTick = None
         # Canvas size when starting to resize canvas
-        self.CanvasStartSize = None        
-        
+        self.CanvasStartSize = None
+
         # List of current displayed contextual buttons
         self.ContextualButtons = []
         # Reference to item for which contextual buttons was displayed
         self.ContextualButtonsItem = None
-        
+
         # Flag indicating that zoom fit current displayed data range or whole
         # data range if False
         self.ZoomFit = False
-        
+
         # Create figure for drawing graphs
         self.Figure = matplotlib.figure.Figure(facecolor='w')
         # Defined border around figure in canvas
-        self.Figure.subplotpars.update(top=0.95, left=0.1, 
+        self.Figure.subplotpars.update(top=0.95, left=0.1,
                                        bottom=0.1, right=0.95)
-        
+
         FigureCanvas.__init__(self, parent, -1, self.Figure)
         self.SetWindowStyle(wx.WANTS_CHARS)
         self.SetBackgroundColour(wx.WHITE)
-        
+
         # Bind wx events
         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
@@ -290,42 +290,42 @@
         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
         self.Bind(wx.EVT_SIZE, self.OnResize)
-        
+
         # Set canvas min size
         canvas_size = self.GetCanvasMinSize()
         self.SetMinSize(canvas_size)
-        
+
         # Define Viewer drop target
         self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
-        
+
         # Connect matplotlib events
         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
         self.mpl_connect('scroll_event', self.OnCanvasScroll)
-        
+
         # Add buttons for zooming on current displayed data range
         self.Buttons.append(
                 GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
-        
+
         # Add buttons for changing canvas size with predefined height
         for size, bitmap in zip(
                 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
                 ["minimize_graph", "middle_graph", "maximize_graph"]):
             self.Buttons.append(
-                    GraphButton(0, 0, bitmap, 
+                    GraphButton(0, 0, bitmap,
                                 self.GetOnChangeSizeButton(size)))
-        
+
         # Add buttons for exporting graph values to clipboard and close graph
         for bitmap, callback in [
                 ("export_graph_mini", self.OnExportGraphButton),
                 ("delete_graph", self.OnCloseButton)]:
             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
-        
+
         # Update graphs elements
         self.ResetGraphics()
         self.RefreshLabelsPosition(canvas_size.height)
-    
+
     def AddItem(self, item):
         """
         Add an item to the list of items displayed by Viewer
@@ -333,14 +333,14 @@
         """
         DebugVariableViewer.AddItem(self, item)
         self.ResetGraphics()
-        
+
     def RemoveItem(self, item):
         """
         Remove an item from the list of items displayed by Viewer
         @param item: Item to remove from the list
         """
         DebugVariableViewer.RemoveItem(self, item)
-        
+
         # If list of items is not empty
         if not self.ItemsIsEmpty():
             # Return to parallel graph if there is only one item
@@ -348,51 +348,51 @@
             if len(self.Items) == 1:
                 self.GraphType = GRAPH_PARALLEL
             self.ResetGraphics()
-    
+
     def SetCursorTick(self, cursor_tick):
         """
         Set cursor tick
         @param cursor_tick: Cursor tick
         """
         self.CursorTick = cursor_tick
-    
+
     def SetZoomFit(self, zoom_fit):
         """
         Set flag indicating that zoom fit current displayed data range
         @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
         """
-        # Flag is different from the actual one 
+        # Flag is different from the actual one
         if zoom_fit != self.ZoomFit:
             # Save new flag value
             self.ZoomFit = zoom_fit
-            
+
             # Update button for zoom fit bitmap
             self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
-            
+
             # Refresh canvas
             self.RefreshViewer()
-        
+
     def SubscribeAllDataConsumers(self):
         """
         Function that unsubscribe and remove every item that store values of
         a variable that doesn't exist in PLC anymore
         """
         DebugVariableViewer.SubscribeAllDataConsumers(self)
-        
+
         # Graph still have data to display
         if not self.ItemsIsEmpty():
             # Reset flag indicating that zoom fit current displayed data range
             self.SetZoomFit(False)
-            
+
             self.ResetGraphics()
-    
+
     def Is3DCanvas(self):
         """
         Return if Viewer is a 3D canvas
         @return: True if Viewer is a 3D canvas
         """
         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
-    
+
     def GetButtons(self):
         """
         Return list of buttons defined in Viewer
@@ -400,7 +400,7 @@
         """
         # Add contextual buttons to default buttons
         return self.Buttons + self.ContextualButtons
-    
+
     def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
         """
         Show contextual menu for item aside a label of this item defined
@@ -412,19 +412,19 @@
         # Return immediately if contextual menu for item is already shown
         if self.ContextualButtonsItem == item:
             return
-        
+
         # Close already shown contextual menu
         self.DismissContextualButtons()
-        
+
         # Save item for which contextual menu is shown
         self.ContextualButtonsItem = item
-        
+
         # If item variable is forced, add button for release variable to
         # contextual menu
         if self.ContextualButtonsItem.IsForced():
             self.ContextualButtons.append(
                 GraphButton(0, 0, "release", self.OnReleaseItemButton))
-        
+
         # Add other buttons to contextual menu
         for bitmap, callback in [
                 ("force", self.OnForceItemButton),
@@ -432,13 +432,13 @@
                 ("delete_graph", self.OnRemoveItemButton)]:
             self.ContextualButtons.append(
                     GraphButton(0, 0, bitmap, callback))
-        
+
         # If buttons are shown at left side or upper side of rect, positions
         # will be set in reverse order
         buttons = self.ContextualButtons[:]
         if direction in [wx.TOP, wx.LEFT]:
              buttons.reverse()
-             
+
         # Set contextual menu buttons position aside rect depending on
         # direction given
         offset = 0
@@ -458,10 +458,10 @@
                 offset += h
             button.SetPosition(x, y)
             button.Show()
-        
+
         # Refresh canvas
         self.ParentWindow.ForceRefresh()
-    
+
     def DismissContextualButtons(self):
         """
         Close current shown contextual menu
@@ -469,14 +469,14 @@
         # Return immediately if no contextual menu is shown
         if self.ContextualButtonsItem is None:
             return
-        
+
         # Reset variables corresponding to contextual menu
         self.ContextualButtonsItem = None
         self.ContextualButtons = []
-        
+
         # Refresh canvas
         self.ParentWindow.ForceRefresh()
-    
+
     def IsOverContextualButton(self, x, y):
         """
         Return if point is over one contextual button of Viewer
@@ -488,7 +488,7 @@
             if button.HitTest(x, y):
                 return button
         return None
-    
+
     def ExportGraph(self, item=None):
         """
         Export item(s) data to clipboard in CSV format
@@ -497,17 +497,17 @@
         """
         self.ParentWindow.CopyDataToClipboard(
             [(item, [entry for entry in item.GetData()])
-             for item in (self.Items 
-                          if item is None 
+             for item in (self.Items
+                          if item is None
                           else [item])])
-    
+
     def OnZoomFitButton(self):
         """
         Function called when Viewer Zoom Fit button is pressed
         """
         # Toggle zoom fit flag value
         self.SetZoomFit(not self.ZoomFit)
-        
+
     def GetOnChangeSizeButton(self, height):
         """
         Function that generate callback function for change Viewer height to
@@ -518,32 +518,32 @@
         def OnChangeSizeButton():
             self.SetCanvasHeight(height)
         return OnChangeSizeButton
-    
+
     def OnExportGraphButton(self):
         """
         Function called when Viewer Export button is pressed
         """
         # Export data of every item in Viewer
         self.ExportGraph()
-    
+
     def OnForceItemButton(self):
         """
         Function called when contextual menu Force button is pressed
         """
-        # Open dialog for forcing item variable value 
+        # Open dialog for forcing item variable value
         self.ForceValue(self.ContextualButtonsItem)
         # Close contextual menu
         self.DismissContextualButtons()
-        
+
     def OnReleaseItemButton(self):
         """
         Function called when contextual menu Release button is pressed
         """
-        # Release item variable value 
+        # Release item variable value
         self.ReleaseValue(self.ContextualButtonsItem)
         # Close contextual menu
         self.DismissContextualButtons()
-    
+
     def OnExportItemGraphButton(self):
         """
         Function called when contextual menu Export button is pressed
@@ -552,17 +552,17 @@
         self.ExportGraph(self.ContextualButtonsItem)
         # Close contextual menu
         self.DismissContextualButtons()
-        
-    def OnRemoveItemButton(self):            
+
+    def OnRemoveItemButton(self):
         """
         Function called when contextual menu Remove button is pressed
         """
         # Remove item from Viewer
-        wx.CallAfter(self.ParentWindow.DeleteValue, self, 
+        wx.CallAfter(self.ParentWindow.DeleteValue, self,
                      self.ContextualButtonsItem)
         # Close contextual menu
         self.DismissContextualButtons()
-    
+
     def HandleCursorMove(self, event):
         """
         Update Cursor position according to mouse position and graph type
@@ -571,7 +571,7 @@
         start_tick, end_tick = self.ParentWindow.GetRange()
         cursor_tick = None
         items = self.ItemsDict.values()
-        
+
         # Graph is orthogonal
         if self.GraphType == GRAPH_ORTHOGONAL:
             # Extract items data displayed in canvas figure
@@ -579,31 +579,31 @@
             end_tick = max(end_tick, start_tick)
             x_data = items[0].GetData(start_tick, end_tick)
             y_data = items[1].GetData(start_tick, end_tick)
-            
+
             # Search for the nearest point from mouse position
             if len(x_data) > 0 and len(y_data) > 0:
-                length = min(len(x_data), len(y_data)) 
+                length = min(len(x_data), len(y_data))
                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \
                                (y_data[:length,1]-event.ydata) ** 2)
-                
+
                 # Set cursor tick to the tick of this point
                 cursor_tick = x_data[numpy.argmin(d), 0]
-        
+
         # Graph is parallel
         else:
             # Extract items tick
             data = items[0].GetData(start_tick, end_tick)
-            
+
             # Search for point that tick is the nearest from mouse X position
             # and set cursor tick to the tick of this point
             if len(data) > 0:
                 cursor_tick = data[numpy.argmin(
                         numpy.abs(data[:,0] - event.xdata)), 0]
-        
+
         # Update cursor tick
         if cursor_tick is not None:
             self.ParentWindow.SetCursorTick(cursor_tick)
-    
+
     def OnCanvasButtonPressed(self, event):
         """
         Function called when a button of mouse is pressed
@@ -613,18 +613,18 @@
         # comparing to wx
         width, height = self.GetSize()
         x, y = event.x, height - event.y
-        
+
         # Return immediately if mouse is over a button
         if self.IsOverButton(x, y):
-            return 
-        
+            return
+
         # Mouse was clicked inside graph figure
         if event.inaxes == self.Axes:
-            
+
             # Find if it was on an item label
             item_idx = None
             # Check every label paired with corresponding item
-            for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
+            for i, t in ([pair for pair in enumerate(self.AxesLabels)] +
                          [pair for pair in enumerate(self.Labels)]):
                 # Get label bounding box
                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
@@ -633,46 +633,46 @@
                 if rect.InsideXY(x, y):
                     item_idx = i
                     break
-            
+
             # If an item label have been clicked
             if item_idx is not None:
                 # Hide buttons and contextual buttons
                 self.ShowButtons(False)
                 self.DismissContextualButtons()
-                
+
                 # Start a drag'n drop from mouse position in wx coordinate of
                 # parent
                 xw, yw = self.GetPosition()
-                self.ParentWindow.StartDragNDrop(self, 
-                    self.ItemsDict.values()[item_idx], 
+                self.ParentWindow.StartDragNDrop(self,
+                    self.ItemsDict.values()[item_idx],
                     x + xw, y + yw, # Current mouse position
                     x + xw, y + yw) # Mouse position when button was clicked
-            
+
             # Don't handle mouse button if canvas is 3D and let matplotlib do
             # the default behavior (rotate 3D axes)
             elif not self.Is3DCanvas():
                 # Save mouse position when clicked
                 self.MouseStartPos = wx.Point(x, y)
-                
+
                 # Mouse button was left button, start moving cursor
                 if event.button == 1:
                     # Save current tick in case a drag'n drop is initiate to
                     # restore it
                     self.StartCursorTick = self.CursorTick
-                    
+
                     self.HandleCursorMove(event)
-                    
+
                 # Mouse button is middle button and graph is parallel, start
                 # moving graph along X coordinate (tick)
                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
                     self.StartCursorTick = self.ParentWindow.GetRange()[0]
-        
+
         # Mouse was clicked outside graph figure and over resize highlight with
         # left button, start resizing Viewer
         elif event.button == 1 and event.y <= 5:
             self.MouseStartPos = wx.Point(x, y)
             self.CanvasStartSize = height
-    
+
     def OnCanvasButtonReleased(self, event):
         """
         Function called when a button of mouse is released
@@ -686,37 +686,37 @@
             # Give mouse position in wx coordinate of parent
             self.ParentWindow.StopDragNDrop(item.GetVariable(),
                 xw + event.x, yw + height - event.y)
-        
+
         else:
             # Reset any move in progress
             self.MouseStartPos = None
             self.CanvasStartSize = None
-            
+
             # Handle button under mouse if it exist
             width, height = self.GetSize()
             self.HandleButton(event.x, height - event.y)
-    
+
     def OnCanvasMotion(self, event):
         """
         Function called when a button of mouse is moved over Viewer
         @param event: Mouse event
         """
         width, height = self.GetSize()
-        
+
         # If a drag'n drop is in progress, move canvas dragged
         if self.ParentWindow.IsDragging():
             xw, yw = self.GetPosition()
             # Give mouse position in wx coordinate of parent
             self.ParentWindow.MoveDragNDrop(
-                xw + event.x, 
+                xw + event.x,
                 yw + height - event.y)
-        
-        # If a Viewer resize is in progress, change Viewer size 
+
+        # If a Viewer resize is in progress, change Viewer size
         elif event.button == 1 and self.CanvasStartSize is not None:
             width, height = self.GetSize()
             self.SetCanvasHeight(
                 self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
-        
+
         # If no button is pressed, show or hide contextual buttons or resize
         # highlight
         elif event.button is None:
@@ -729,13 +729,13 @@
                              wx.LEFT, wx.BOTTOM] # Directions for Labels
             else: # Graph is orthogonal in 3D
                 directions = [wx.LEFT] * len(self.Labels)
-            
+
             # Find if mouse is over an item label
             item_idx = None
             menu_direction = None
             for (i, t), dir in zip(
-                    [pair for pair in enumerate(self.AxesLabels)] + 
-                    [pair for pair in enumerate(self.Labels)], 
+                    [pair for pair in enumerate(self.AxesLabels)] +
+                    [pair for pair in enumerate(self.Labels)],
                     directions):
                 # Check every label paired with corresponding item
                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
@@ -745,19 +745,19 @@
                     item_idx = i
                     menu_direction = dir
                     break
-            
-            # If mouse is over an item label, 
+
+            # If mouse is over an item label,
             if item_idx is not None:
                 self.PopupContextualButtons(
-                    self.ItemsDict.values()[item_idx], 
+                    self.ItemsDict.values()[item_idx],
                     rect, menu_direction)
                 return
-            
+
             # If mouse isn't over a contextual menu, hide the current shown one
-            # if it exists 
+            # if it exists
             if self.IsOverContextualButton(event.x, height - event.y) is None:
                 self.DismissContextualButtons()
-            
+
             # Update resize highlight
             if event.y <= 5:
                 if self.SetHighlight(HIGHLIGHT_RESIZE):
@@ -767,46 +767,46 @@
                 if self.SetHighlight(HIGHLIGHT_NONE):
                     self.SetCursor(wx.NullCursor)
                     self.ParentWindow.ForceRefresh()
-        
-        # Handle buttons if canvas is not 3D 
+
+        # Handle buttons if canvas is not 3D
         elif not self.Is3DCanvas():
-            
+
             # If left button is pressed
             if event.button == 1:
-                
+
                 # Mouse is inside graph figure
                 if event.inaxes == self.Axes:
-                    
+
                     # If a cursor move is in progress, update cursor position
                     if self.MouseStartPos is not None:
                         self.HandleCursorMove(event)
-                
+
                 # Mouse is outside graph figure, cursor move is in progress and
                 # there is only one item in Viewer, start a drag'n drop
                 elif self.MouseStartPos is not None and len(self.Items) == 1:
                     xw, yw = self.GetPosition()
                     self.ParentWindow.SetCursorTick(self.StartCursorTick)
-                    self.ParentWindow.StartDragNDrop(self, 
+                    self.ParentWindow.StartDragNDrop(self,
                         self.ItemsDict.values()[0],
                         # Current mouse position
                         event.x + xw, height - event.y + yw,
                         # Mouse position when button was clicked
                         self.MouseStartPos.x + xw,
                         self.MouseStartPos.y + yw)
-            
+
             # If middle button is pressed and moving graph along X coordinate
             # is in progress
             elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
                  self.MouseStartPos is not None:
                 start_tick, end_tick = self.ParentWindow.GetRange()
                 rect = self.GetAxesBoundingBox()
-                
+
                 # Move graph along X coordinate
                 self.ParentWindow.SetCanvasPosition(
-                    self.StartCursorTick + 
+                    self.StartCursorTick +
                     (self.MouseStartPos.x - event.x) *
                     (end_tick - start_tick) / rect.width)
-    
+
     def OnCanvasScroll(self, event):
         """
         Function called when a wheel mouse is use in Viewer
@@ -815,7 +815,7 @@
         # Change X range of graphs if mouse is in canvas figure and ctrl is
         # pressed
         if event.inaxes is not None and event.guiEvent.ControlDown():
-            
+
             # Calculate position of fixed tick point according to graph type
             # and mouse position
             if self.GraphType == GRAPH_ORTHOGONAL:
@@ -824,10 +824,10 @@
             else:
                 tick = event.xdata
             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
-            
+
             # Vetoing event to prevent parent panel to be scrolled
             self.ParentWindow.VetoScrollEvent = True
-    
+
     def OnLeftDClick(self, event):
         """
         Function called when a left mouse button is double clicked
@@ -841,17 +841,17 @@
             self.ParentWindow.SetCursorTick(self.StartCursorTick)
             # Toggle to text Viewer(s)
             self.ParentWindow.ToggleViewerType(self)
-        
+
         else:
             event.Skip()
-    
+
     # Cursor tick move for each arrow key
     KEY_CURSOR_INCREMENT = {
         wx.WXK_LEFT: -1,
         wx.WXK_RIGHT: 1,
         wx.WXK_UP: 10,
         wx.WXK_DOWN: -10}
-    
+
     def OnKeyDown(self, event):
         """
         Function called when key is pressed
@@ -863,7 +863,7 @@
             if move is not None:
                 self.ParentWindow.MoveCursorTick(move)
         event.Skip()
-    
+
     def OnLeave(self, event):
         """
         Function called when mouse leave Viewer
@@ -876,7 +876,7 @@
             DebugVariableViewer.OnLeave(self, event)
         else:
             event.Skip()
-    
+
     def GetCanvasMinSize(self):
         """
         Return the minimum size of Viewer so that all items label can be
@@ -885,10 +885,10 @@
         """
         # The minimum height take in account the height of all items, padding
         # inside figure and border around figure
-        return wx.Size(200, 
-            CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
+        return wx.Size(200,
+            CANVAS_BORDER[0] + CANVAS_BORDER[1] +
             2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
-    
+
     def SetCanvasHeight(self, height):
         """
         Set Viewer size checking that it respects Viewer minimum size
@@ -899,7 +899,7 @@
         self.SetMinSize(wx.Size(min_width, height))
         self.RefreshLabelsPosition(height)
         self.ParentWindow.RefreshGraphicsSizer()
-        
+
     def GetAxesBoundingBox(self, parent_coordinate=False):
         """
         Return figure bounding box in wx coordinate
@@ -911,15 +911,15 @@
         ax, ay, aw, ah = self.figure.gca().get_position().bounds
         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
                        aw * width + 2, ah * height + 1)
-        
+
         # If parent_coordinate, add Viewer position in parent
         if parent_coordinate:
             xw, yw = self.GetPosition()
             bbox.x += xw
             bbox.y += yw
-        
+
         return bbox
-    
+
     def RefreshHighlight(self, x, y):
         """
         Refresh Viewer highlight according to mouse position
@@ -927,7 +927,7 @@
         @param y: Y coordinate of mouse pointer
         """
         width, height = self.GetSize()
-        
+
         # Mouse is over Viewer figure and graph is not 3D
         bbox = self.GetAxesBoundingBox()
         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
@@ -935,28 +935,28 @@
             # Mouse is over Viewer left part of figure
             if rect.InsideXY(x, y):
                 self.SetHighlight(HIGHLIGHT_LEFT)
-            
+
             # Mouse is over Viewer right part of figure
             else:
                 self.SetHighlight(HIGHLIGHT_RIGHT)
-        
+
         # Mouse is over upper part of Viewer
         elif y < height / 2:
             # Viewer is upper one in Debug Variable Panel, show highlight
             if self.ParentWindow.IsViewerFirst(self):
                 self.SetHighlight(HIGHLIGHT_BEFORE)
-            
+
             # Viewer is not the upper one, show highlight in previous one
             # It prevents highlight to move when mouse leave one Viewer to
             # another
             else:
                 self.SetHighlight(HIGHLIGHT_NONE)
                 self.ParentWindow.HighlightPreviousViewer(self)
-        
+
         # Mouse is over lower part of Viewer
         else:
             self.SetHighlight(HIGHLIGHT_AFTER)
-    
+
     def OnAxesMotion(self, event):
         """
         Function overriding default function called when mouse is dragged for
@@ -969,7 +969,7 @@
             if current_time - self.LastMotionTime > REFRESH_PERIOD:
                 self.LastMotionTime = current_time
                 Axes3D._on_move(self.Axes, event)
-    
+
     def GetAddTextFunction(self):
         """
         Return function for adding text in figure according to graph type
@@ -987,58 +987,58 @@
             self.Axes.set_prop_cycle(cycler('color',color))
         else:
             self.Axes.set_color_cycle(color)
-        
+
     def ResetGraphics(self):
         """
         Reset figure and graphical elements displayed in it
-        Called any time list of items or graph type change 
+        Called any time list of items or graph type change
         """
         # Clear figure from any axes defined
         self.Figure.clear()
-        
+
         # Add 3D projection if graph is in 3D
         if self.Is3DCanvas():
             self.Axes = self.Figure.gca(projection='3d')
             self.SetAxesColor(['b'])
-            
-            # Override function to prevent too much refresh when graph is 
+
+            # Override function to prevent too much refresh when graph is
             # rotated
             self.LastMotionTime = gettime()
             setattr(self.Axes, "_on_move", self.OnAxesMotion)
-            
+
             # Init graph mouse event so that graph can be rotated
             self.Axes.mouse_init()
-            
+
             # Set size of Z axis labels
             self.Axes.tick_params(axis='z', labelsize='small')
-        
+
         else:
             self.Axes = self.Figure.gca()
             self.SetAxesColor(COLOR_CYCLE)
-        
+
         # Set size of X and Y axis labels
         self.Axes.tick_params(axis='x', labelsize='small')
         self.Axes.tick_params(axis='y', labelsize='small')
-        
+
         # Init variables storing graphical elements added to figure
         self.Plots = []      # List of curves
         self.VLine = None    # Vertical line for cursor
         self.HLine = None    # Horizontal line for cursor (only orthogonal 2D)
         self.AxesLabels = [] # List of items variable path text label
         self.Labels = []     # List of items text label
-        
-        # Get function to add a text in figure according to graph type 
+
+        # Get function to add a text in figure according to graph type
         add_text_func = self.GetAddTextFunction()
-        
+
         # Graph type is parallel or orthogonal in 3D
         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
             num_item = len(self.Items)
             for idx in xrange(num_item):
-                
+
                 # Get color from color cycle (black if only one item)
                 color = ('k' if num_item == 1
                              else COLOR_CYCLE[idx % len(COLOR_CYCLE)])
-                
+
                 # In 3D graph items variable label are not displayed as text
                 # in figure, but as axis title
                 if not self.Is3DCanvas():
@@ -1046,12 +1046,12 @@
                     self.AxesLabels.append(
                         add_text_func(size='small', color=color,
                                       verticalalignment='top'))
-                
+
                 # Items variable labels are in figure lower right corner
                 self.Labels.append(
-                    add_text_func(size='large', color=color, 
+                    add_text_func(size='large', color=color,
                                   horizontalalignment='right'))
-        
+
         # Graph type is orthogonal in 2D
         else:
             # X coordinate labels are in figure lower side
@@ -1059,7 +1059,7 @@
             self.Labels.append(
                 add_text_func(size='large',
                               horizontalalignment='right'))
-            
+
             # Y coordinate labels are vertical and in figure left side
             self.AxesLabels.append(
                 add_text_func(size='small', rotation='vertical',
@@ -1067,11 +1067,11 @@
             self.Labels.append(
                 add_text_func(size='large', rotation='vertical',
                               verticalalignment='top'))
-       
+
         # Refresh position of labels according to Viewer size
         width, height = self.GetSize()
         self.RefreshLabelsPosition(height)
-    
+
     def RefreshLabelsPosition(self, height):
         """
         Function called when mouse leave Viewer
@@ -1087,30 +1087,30 @@
         graph_ratio = 1. / (
             (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio)
              * height)             # Divide by figure height in pixel
-        
+
         # Update position of figure (keeping up and bottom border the same
         # size)
         self.Figure.subplotpars.update(
-            top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
+            top= 1.0 - CANVAS_BORDER[1] * canvas_ratio,
             bottom= CANVAS_BORDER[0] * canvas_ratio)
-        
+
         # Update position of items labels
         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
             num_item = len(self.Items)
             for idx in xrange(num_item):
-                
+
                 # In 3D graph items variable label are not displayed
                 if not self.Is3DCanvas():
                     # Items variable labels are in figure upper left corner
                     self.AxesLabels[idx].set_position(
-                        (0.05, 
-                         1.0 - (CANVAS_PADDING + 
+                        (0.05,
+                         1.0 - (CANVAS_PADDING +
                                 AXES_LABEL_HEIGHT * idx) * graph_ratio))
-                
+
                 # Items variable labels are in figure lower right corner
                 self.Labels[idx].set_position(
-                    (0.95, 
-                     CANVAS_PADDING * graph_ratio + 
+                    (0.95,
+                     CANVAS_PADDING * graph_ratio +
                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
         else:
             # X coordinate labels are in figure lower side
@@ -1118,16 +1118,16 @@
                     (0.1, CANVAS_PADDING * graph_ratio))
             self.Labels[0].set_position(
                     (0.95, CANVAS_PADDING * graph_ratio))
-            
+
             # Y coordinate labels are vertical and in figure left side
             self.AxesLabels[1].set_position(
                     (0.05, 2 * CANVAS_PADDING * graph_ratio))
             self.Labels[1].set_position(
                     (0.05, 1.0 - CANVAS_PADDING * graph_ratio))
-        
+
         # Update subplots
         self.Figure.subplots_adjust()
-    
+
     def RefreshViewer(self, refresh_graphics=True):
         """
         Function called to refresh displayed by matplotlib canvas
@@ -1138,55 +1138,55 @@
         if refresh_graphics:
             # Get tick range of values to display
             start_tick, end_tick = self.ParentWindow.GetRange()
-            
+
             # Graph is parallel
-            if self.GraphType == GRAPH_PARALLEL:    
+            if self.GraphType == GRAPH_PARALLEL:
                 # Init list of data range for each variable displayed
                 ranges = []
-                
+
                 # Get data and range for each variable displayed
                 for idx, item in enumerate(self.Items):
                     data, min_value, max_value = item.GetDataAndValueRange(
                                 start_tick, end_tick, not self.ZoomFit)
-                    
+
                     # Check that data is not empty
                     if data is not None:
                         # Add variable range to list of variable data range
                         ranges.append((min_value, max_value))
-                        
+
                         # Add plot to canvas if not yet created
                         if len(self.Plots) <= idx:
                             self.Plots.append(
                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
-                        
+
                         # Set data to already created plot in canvas
                         else:
                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
-                
+
                 # Get X and Y axis ranges
                 x_min, x_max = start_tick, end_tick
                 y_min, y_max = merge_ranges(ranges)
-                
+
                 # Display cursor in canvas if a cursor tick is defined and it is
                 # include in values tick range
-                if (self.CursorTick is not None and 
+                if (self.CursorTick is not None and
                     start_tick <= self.CursorTick <= end_tick):
-                    
+
                     # Define a vertical line to display cursor position if no
                     # line is already defined
                     if self.VLine is None:
-                        self.VLine = self.Axes.axvline(self.CursorTick, 
+                        self.VLine = self.Axes.axvline(self.CursorTick,
                                                        color=CURSOR_COLOR)
-                    
+
                     # Set value of vertical line if already defined
                     else:
                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
                     self.VLine.set_visible(True)
-                
+
                 # Hide vertical line if cursor tick is not defined or reset
                 elif self.VLine is not None:
                     self.VLine.set_visible(False)
-            
+
             # Graph is orthogonal
             else:
                 # Update tick range, removing ticks that don't have a value for
@@ -1194,76 +1194,76 @@
                 start_tick = max(start_tick, self.GetItemsMinCommonTick())
                 end_tick = max(end_tick, start_tick)
                 items = self.ItemsDict.values()
-                
+
                 # Get data and range for first variable (X coordinate)
                 x_data, x_min, x_max = items[0].GetDataAndValueRange(
                                         start_tick, end_tick, not self.ZoomFit)
                 # Get data and range for second variable (Y coordinate)
                 y_data, y_min, y_max = items[1].GetDataAndValueRange(
                                         start_tick, end_tick, not self.ZoomFit)
-                
+
                 # Normalize X and Y coordinates value range
                 x_min, x_max = merge_ranges([(x_min, x_max)])
                 y_min, y_max = merge_ranges([(y_min, y_max)])
-                
-                # Get X and Y coordinates for cursor if cursor tick is defined 
+
+                # Get X and Y coordinates for cursor if cursor tick is defined
                 if self.CursorTick is not None:
                     x_cursor, x_forced = items[0].GetValue(
                                             self.CursorTick, raw=True)
                     y_cursor, y_forced = items[1].GetValue(
                                             self.CursorTick, raw=True)
-                
+
                 # Get common data length so that each value has an x and y
                 # coordinate
                 length = (min(len(x_data), len(y_data))
                           if x_data is not None and y_data is not None
                           else 0)
-                
-                # Graph is orthogonal 2D 
+
+                # Graph is orthogonal 2D
                 if len(self.Items) < 3:
-                    
+
                     # Check that x and y data are not empty
                     if x_data is not None and y_data is not None:
-                        
+
                         # Add plot to canvas if not yet created
                         if len(self.Plots) == 0:
                             self.Plots.append(
-                                self.Axes.plot(x_data[:, 1][:length], 
+                                self.Axes.plot(x_data[:, 1][:length],
                                                y_data[:, 1][:length])[0])
-                        
+
                         # Set data to already created plot in canvas
                         else:
                             self.Plots[0].set_data(
-                                x_data[:, 1][:length], 
+                                x_data[:, 1][:length],
                                 y_data[:, 1][:length])
-                    
+
                     # Display cursor in canvas if a cursor tick is defined and it is
                     # include in values tick range
-                    if (self.CursorTick is not None and 
+                    if (self.CursorTick is not None and
                         start_tick <= self.CursorTick <= end_tick):
-                        
+
                         # Define a vertical line to display cursor x coordinate
                         # if no line is already defined
                         if self.VLine is None:
-                            self.VLine = self.Axes.axvline(x_cursor, 
+                            self.VLine = self.Axes.axvline(x_cursor,
                                                            color=CURSOR_COLOR)
                         # Set value of vertical line if already defined
                         else:
                             self.VLine.set_xdata((x_cursor, x_cursor))
-                        
-                        
+
+
                         # Define a horizontal line to display cursor y
                         # coordinate if no line is already defined
                         if self.HLine is None:
-                            self.HLine = self.Axes.axhline(y_cursor, 
+                            self.HLine = self.Axes.axhline(y_cursor,
                                                            color=CURSOR_COLOR)
                         # Set value of horizontal line if already defined
                         else:
                             self.HLine.set_ydata((y_cursor, y_cursor))
-                        
+
                         self.VLine.set_visible(True)
                         self.HLine.set_visible(True)
-                    
+
                     # Hide vertical and horizontal line if cursor tick is not
                     # defined or reset
                     else:
@@ -1271,42 +1271,42 @@
                             self.VLine.set_visible(False)
                         if self.HLine is not None:
                             self.HLine.set_visible(False)
-                
+
                 # Graph is orthogonal 3D
                 else:
                     # Remove all plots already defined in 3D canvas
                     while len(self.Axes.lines) > 0:
                         self.Axes.lines.pop()
-                    
+
                     # Get data and range for third variable (Z coordinate)
                     z_data, z_min, z_max = items[2].GetDataAndValueRange(
                                     start_tick, end_tick, not self.ZoomFit)
-                    
+
                     # Normalize Z coordinate value range
                     z_min, z_max = merge_ranges([(z_min, z_max)])
-                    
+
                     # Check that x, y and z data are not empty
-                    if (x_data is not None and y_data is not None and 
+                    if (x_data is not None and y_data is not None and
                         z_data is not None):
-                        
+
                         # Get common data length so that each value has an x, y
                         # and z coordinate
                         length = min(length, len(z_data))
-                        
+
                         # Add plot to canvas
                         self.Axes.plot(x_data[:, 1][:length],
                                        y_data[:, 1][:length],
                                        zs = z_data[:, 1][:length])
-                    
+
                     # Display cursor in canvas if a cursor tick is defined and
                     # it is include in values tick range
-                    if (self.CursorTick is not None and 
+                    if (self.CursorTick is not None and
                         start_tick <= self.CursorTick <= end_tick):
-                        
+
                         # Get Z coordinate for cursor
                         z_cursor, z_forced = items[2].GetValue(
                                                 self.CursorTick, raw=True)
-                        
+
                         # Add 3 lines parallel to x, y and z axis to display
                         # cursor position in 3D
                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
@@ -1319,14 +1319,14 @@
                                 kwargs.setdefault(param, value)
                             kwargs["color"] = CURSOR_COLOR
                             self.Axes.plot(**kwargs)
-                    
+
                     # Set Z axis limits
                     self.Axes.set_zlim(z_min, z_max)
-            
+
             # Set X and Y axis limits
             self.Axes.set_xlim(x_min, x_max)
             self.Axes.set_ylim(y_min, y_max)
-        
+
         # Get value and forced flag for each variable displayed in graph
         # If cursor tick is not defined get value and flag of last received
         # or get value and flag of variable at cursor tick
@@ -1335,34 +1335,34 @@
                  if self.CursorTick is not None
                  else (item.GetValue(), item.IsForced()))
                 for item in self.Items])
-        
+
         # Get path of each variable displayed simplified using panel variable
         # name mask
-        labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask()) 
+        labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask())
                   for item in self.Items]
-        
-        # Get style for each variable according to 
+
+        # Get style for each variable according to
         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
-        
+
         # Graph is orthogonal 3D, set variables path as 3D axis label
         if self.Is3DCanvas():
-            for idx, label_func in enumerate([self.Axes.set_xlabel, 
+            for idx, label_func in enumerate([self.Axes.set_xlabel,
                                               self.Axes.set_ylabel,
                                               self.Axes.set_zlabel]):
                 label_func(labels[idx], fontdict={'size': 'small',
                                                   'color': COLOR_CYCLE[idx]})
-        
+
         # Graph is not orthogonal 3D, set variables path in axes labels
         else:
             for label, text in zip(self.AxesLabels, labels):
                 label.set_text(text)
-        
+
         # Set value label text and style according to value and forced flag for
         # each variable displayed
         for label, value, style in zip(self.Labels, values, styles):
             label.set_text(value)
             label.set_style(style)
-        
+
         # Refresh figure
         self.draw()
 
@@ -1372,51 +1372,49 @@
         """
         # Render figure using agg
         FigureCanvasAgg.draw(self)
-        
+
         # Get bitmap of figure rendered
         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
-        if wx.VERSION < (3, 0, 0):        
+        if wx.VERSION < (3, 0, 0):
             self.bitmap.UseAlpha()
-        
+
         # Create DC for rendering graphics in bitmap
         destDC = wx.MemoryDC()
         destDC.SelectObject(self.bitmap)
-        
+
         # Get Graphics Context for DC, for anti-aliased and transparent
         # rendering
         destGC = wx.GCDC(destDC)
-        
+
         destGC.BeginDrawing()
-        
+
         # Get canvas size and figure bounding box in canvas
         width, height = self.GetSize()
         bbox = self.GetAxesBoundingBox()
-        
+
         # If highlight to display is resize, draw thick grey line at bottom
-        # side of canvas 
+        # side of canvas
         if self.Highlight == HIGHLIGHT_RESIZE:
             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
             destGC.DrawRectangle(0, height - 5, width, 5)
-        
+
         # If highlight to display is merging graph, draw 50% transparent blue
         # rectangle on left or right part of figure depending on highlight type
         elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
             destGC.SetPen(HIGHLIGHT_DROP_PEN)
             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
-            
-            x_offset = (bbox.width / 2 
+
+            x_offset = (bbox.width / 2
                         if self.Highlight == HIGHLIGHT_RIGHT
                         else 0)
-            destGC.DrawRectangle(bbox.x + x_offset, bbox.y, 
+            destGC.DrawRectangle(bbox.x + x_offset, bbox.y,
                                  bbox.width / 2, bbox.height)
-        
+
         # Draw other Viewer common elements
         self.DrawCommonElements(destGC, self.GetButtons())
-        
+
         destGC.EndDrawing()
-        
+
         self._isDrawn = True
         self.gui_repaint(drawDC=drawDC)
-    
-