Laurent@814: #!/usr/bin/env python
Laurent@814: # -*- coding: utf-8 -*-
Laurent@814: 
andrej@1571: # This file is part of Beremiz, a Integrated Development Environment for
andrej@1571: # programming IEC 61131-3 automates supporting plcopen standard and CanFestival.
Laurent@814: #
andrej@1571: # Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
Laurent@814: #
andrej@1571: # See COPYING file for copyrights details.
Laurent@814: #
andrej@1571: # This program is free software; you can redistribute it and/or
andrej@1571: # modify it under the terms of the GNU General Public License
andrej@1571: # as published by the Free Software Foundation; either version 2
andrej@1571: # of the License, or (at your option) any later version.
Laurent@814: #
andrej@1571: # This program is distributed in the hope that it will be useful,
andrej@1571: # but WITHOUT ANY WARRANTY; without even the implied warranty of
andrej@1571: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
andrej@1571: # GNU General Public License for more details.
Laurent@814: #
andrej@1571: # You should have received a copy of the GNU General Public License
andrej@1571: # along with this program; if not, write to the Free Software
andrej@1571: # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Laurent@814: 
Laurent@1198: from types import TupleType
Laurent@887: from time import time as gettime
Laurent@887: import numpy
Laurent@814: 
Laurent@814: import wx
Laurent@888: 
Laurent@1198: import matplotlib
Laurent@1198: import matplotlib.pyplot
Laurent@1198: from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
Laurent@1198: from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
Laurent@1198: from matplotlib.backends.backend_agg import FigureCanvasAgg
Laurent@1198: from mpl_toolkits.mplot3d import Axes3D
Laurent@1200: 
Laurent@1200: from editors.DebugViewer import REFRESH_PERIOD
Laurent@814: 
Laurent@1199: from DebugVariableItem import DebugVariableItem
Laurent@1200: from DebugVariableViewer import *
Laurent@1199: from GraphButton import GraphButton
Laurent@1199: 
andrej@1497: 
andrej@1497: from distutils.version import LooseVersion
andrej@1497: if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"):
andrej@1497:     from cycler import cycler
andrej@1497: 
andrej@1497: 
Laurent@1209: # Graph variable display type
Laurent@1198: GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
Laurent@1198: 
Laurent@1209: # Canvas height
Laurent@1198: [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
Laurent@1198: 
Laurent@1209: CANVAS_BORDER = (20., 10.) # Border height on at bottom and top of graph
Laurent@1209: CANVAS_PADDING = 8.5       # Border inside graph where no label is drawn
Laurent@1209: VALUE_LABEL_HEIGHT = 17.   # Height of variable label in graph
Laurent@1209: AXES_LABEL_HEIGHT = 12.75  # Height of variable value in graph
Laurent@1209: 
Laurent@1209: # Colors used cyclically for graph curves
Laurent@1200: COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k']
Laurent@1209: # Color for graph cursor
Laurent@1200: CURSOR_COLOR = '#800080'
Laurent@1198: 
Laurent@1209: #-------------------------------------------------------------------------------
Laurent@1267: #                      Debug Variable Graphic Viewer Helpers
Laurent@1267: #-------------------------------------------------------------------------------
Laurent@1267: 
Laurent@1267: def merge_ranges(ranges):
Laurent@1267:     """
Laurent@1267:     Merge variables data range in a list to return a range of minimal min range
Laurent@1267:     value and maximal max range value extended of 10% for keeping a padding
Laurent@1267:     around graph in canvas
Laurent@1267:     @param ranges: [(range_min_value, range_max_value),...]
Laurent@1267:     @return: merged_range_min_value, merged_range_max_value
Laurent@1267:     """
Laurent@1267:     # Get minimal and maximal range value
Laurent@1267:     min_value = max_value = None
Laurent@1267:     for range_min, range_max in ranges:
Laurent@1267:         # Update minimal range value
Laurent@1267:         if min_value is None:
Laurent@1267:             min_value = range_min
Laurent@1267:         elif range_min is not None:
Laurent@1267:             min_value = min(min_value, range_min)
Laurent@1267:         
Laurent@1267:         # Update maximal range value
Laurent@1267:         if max_value is None:
Laurent@1267:             max_value = range_max
Laurent@1267:         elif range_min is not None:
Laurent@1267:             max_value = max(max_value, range_max)
Laurent@1267:     
Laurent@1267:     # Calculate range center and width if at least one valid range is defined
Laurent@1267:     if min_value is not None and max_value is not None:
Laurent@1267:         center = (min_value + max_value) / 2.
Laurent@1267:         range_size = max(1.0, max_value - min_value)
Laurent@1267:     
Laurent@1267:     # Set default center and with if no valid range is defined
Laurent@1267:     else:
Laurent@1267:         center = 0.5
Laurent@1267:         range_size = 1.0
Laurent@1267:     
Laurent@1267:     # Return range expended from 10 %
Laurent@1267:     return center - range_size * 0.55, center + range_size * 0.55
Laurent@1267: 
Laurent@1267: #-------------------------------------------------------------------------------
Laurent@1209: #                   Debug Variable Graphic Viewer Drop Target
Laurent@1209: #-------------------------------------------------------------------------------
Laurent@1209: 
Laurent@1209: """
Laurent@1215: Class that implements a custom drop target class for Debug Variable Graphic
Laurent@1215: Viewer
Laurent@1209: """
Laurent@1209: 
Laurent@1209: class DebugVariableGraphicDropTarget(wx.TextDropTarget):
Laurent@1200:     
Laurent@1200:     def __init__(self, parent, window):
Laurent@1209:         """
Laurent@1209:         Constructor
Laurent@1209:         @param parent: Reference to Debug Variable Graphic Viewer
Laurent@1209:         @param window: Reference to the Debug Variable Panel
Laurent@1209:         """
Laurent@1200:         wx.TextDropTarget.__init__(self)
Laurent@1200:         self.ParentControl = parent
Laurent@1198:         self.ParentWindow = window
Laurent@1200:         
Laurent@1198:     def __del__(self):
Laurent@1209:         """
Laurent@1209:         Destructor
Laurent@1209:         """
Laurent@1209:         # Remove reference to Debug Variable Graphic Viewer and Debug Variable
Laurent@1209:         # Panel
Laurent@1200:         self.ParentControl = None
Laurent@1198:         self.ParentWindow = None
Laurent@1200:         
Laurent@1200:     def OnDragOver(self, x, y, d):
Laurent@1209:         """
Laurent@1209:         Function called when mouse is dragged over Drop Target
Laurent@1209:         @param x: X coordinate of mouse pointer
Laurent@1209:         @param y: Y coordinate of mouse pointer
Laurent@1209:         @param d: Suggested default for return value
Laurent@1209:         """
Laurent@1209:         # Signal parent that mouse is dragged over
Laurent@1200:         self.ParentControl.OnMouseDragging(x, y)
Laurent@1209:         
Laurent@1200:         return wx.TextDropTarget.OnDragOver(self, x, y, d)
Laurent@1200:         
Laurent@1200:     def OnDropText(self, x, y, data):
Laurent@1209:         """
Laurent@1215:         Function called when mouse is released in Drop Target
Laurent@1209:         @param x: X coordinate of mouse pointer
Laurent@1209:         @param y: Y coordinate of mouse pointer
Laurent@1209:         @param data: Text associated to drag'n drop
Laurent@1209:         """
Laurent@1218:         # Signal Debug Variable Panel to reset highlight
Laurent@1218:         self.ParentWindow.ResetHighlight()
Laurent@1218:         
Laurent@1200:         message = None
Laurent@1209:         
Laurent@1209:         # Check that data is valid regarding DebugVariablePanel
Laurent@1200:         try:
Laurent@1200:             values = eval(data)
Laurent@1200:             if not isinstance(values, TupleType):
Laurent@1207:                 raise ValueError
Laurent@1200:         except:
Laurent@1200:             message = _("Invalid value \"%s\" for debug variable")%data
Laurent@1200:             values = None
Laurent@1200:         
Laurent@1209:         # Display message if data is invalid
Laurent@1200:         if message is not None:
Laurent@1200:             wx.CallAfter(self.ShowMessage, message)
Laurent@1200:         
Laurent@1209:         # Data contain a reference to a variable to debug
Laurent@1200:         elif values[1] == "debug":
Laurent@1200:             target_idx = self.ParentControl.GetIndex()
Laurent@1209:             
Laurent@1209:             # If mouse is dropped in graph canvas bounding box and graph is
Laurent@1209:             # not 3D canvas, graphs will be merged
Laurent@1209:             rect = self.ParentControl.GetAxesBoundingBox()
Laurent@1209:             if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y):
Laurent@1209:                 # Default merge type is parallel
Laurent@1209:                 merge_type = GRAPH_PARALLEL
Laurent@1209:                 
Laurent@1209:                 # If mouse is dropped in left part of graph canvas, graph
Laurent@1209:                 # wall be merged orthogonally
Laurent@1209:                 merge_rect = wx.Rect(rect.x, rect.y, 
Laurent@1209:                                      rect.width / 2., rect.height)
Laurent@1209:                 if merge_rect.InsideXY(x, y):
Laurent@1209:                     merge_type = GRAPH_ORTHOGONAL
Laurent@1209:                 
Laurent@1209:                 # Merge graphs
Laurent@1209:                 wx.CallAfter(self.ParentWindow.MergeGraphs, 
Laurent@1209:                              values[0], target_idx, 
Laurent@1209:                              merge_type, force=True)
Laurent@1209:                 
Laurent@1209:             else:
Laurent@1209:                 width, height = self.ParentControl.GetSize()
Laurent@1209:                 
Laurent@1209:                 # Get Before which Viewer the variable has to be moved or added
Laurent@1209:                 # according to the position of mouse in Viewer.
Laurent@1200:                 if y > height / 2:
Laurent@1200:                     target_idx += 1
Laurent@1209:                 
Laurent@1209:                 # Drag'n Drop is an internal is an internal move inside Debug
Laurent@1209:                 # Variable Panel 
Laurent@1209:                 if len(values) > 2 and values[2] == "move":
Laurent@1209:                     self.ParentWindow.MoveValue(values[0], 
Laurent@1209:                                                 target_idx)
Laurent@1209:                 
Laurent@1209:                 # Drag'n Drop was initiated by another control of Beremiz
Laurent@1200:                 else:
Laurent@1209:                     self.ParentWindow.InsertValue(values[0], 
Laurent@1209:                                                   target_idx, 
Laurent@1209:                                                   force=True)
Laurent@1200:     
Laurent@1200:     def OnLeave(self):
Laurent@1209:         """
Laurent@1209:         Function called when mouse is leave Drop Target
Laurent@1209:         """
Laurent@1209:         # Signal Debug Variable Panel to reset highlight
Laurent@1200:         self.ParentWindow.ResetHighlight()
Laurent@1200:         return wx.TextDropTarget.OnLeave(self)
Laurent@1200:     
Laurent@1200:     def ShowMessage(self, message):
Laurent@1209:         """
Laurent@1209:         Show error message in Error Dialog
Laurent@1209:         @param message: Error message to display
Laurent@1209:         """
Laurent@1227:         dialog = wx.MessageDialog(self.ParentWindow, 
Laurent@1227:                                   message, 
Laurent@1227:                                   _("Error"), 
Laurent@1227:                                   wx.OK|wx.ICON_ERROR)
Laurent@1200:         dialog.ShowModal()
Laurent@1200:         dialog.Destroy()
Laurent@1200: 
Laurent@1200: 
Laurent@1209: #-------------------------------------------------------------------------------
Laurent@1209: #                      Debug Variable Graphic Viewer Class
Laurent@1209: #-------------------------------------------------------------------------------
Laurent@1209: 
Laurent@1209: """
Laurent@1209: Class that implements a Viewer that display variable values as a graphs
Laurent@1209: """
Laurent@1209: 
Laurent@1200: class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
Laurent@1198:     
Laurent@1198:     def __init__(self, parent, window, items, graph_type):
Laurent@1209:         """
Laurent@1209:         Constructor
Laurent@1209:         @param parent: Parent wx.Window of DebugVariableText
Laurent@1209:         @param window: Reference to the Debug Variable Panel
Laurent@1209:         @param items: List of DebugVariableItem displayed by Viewer
Laurent@1209:         @param graph_type: Graph display type (Parallel or orthogonal)
Laurent@1209:         """
Laurent@1198:         DebugVariableViewer.__init__(self, window, items)
Laurent@1198:         
Laurent@1209:         self.GraphType = graph_type        # Graph type display
Laurent@1209:         self.CursorTick = None             # Tick of the graph cursor
Laurent@1209:         
Laurent@1209:         # Mouse position when start dragging
Laurent@1198:         self.MouseStartPos = None
Laurent@1209:         # Tick when moving tick start
Laurent@1198:         self.StartCursorTick = None
Laurent@1209:         # Canvas size when starting to resize canvas
Laurent@1209:         self.CanvasStartSize = None        
Laurent@1209:         
Laurent@1209:         # List of current displayed contextual buttons
Laurent@1198:         self.ContextualButtons = []
Laurent@1209:         # Reference to item for which contextual buttons was displayed
Laurent@1198:         self.ContextualButtonsItem = None
Laurent@1198:         
Laurent@1267:         # Flag indicating that zoom fit current displayed data range or whole
Laurent@1267:         # data range if False
Laurent@1267:         self.ZoomFit = False
Laurent@1267:         
Laurent@1209:         # Create figure for drawing graphs
Laurent@1198:         self.Figure = matplotlib.figure.Figure(facecolor='w')
Laurent@1209:         # Defined border around figure in canvas
Laurent@1209:         self.Figure.subplotpars.update(top=0.95, left=0.1, 
Laurent@1209:                                        bottom=0.1, right=0.95)
Laurent@1198:         
Laurent@1198:         FigureCanvas.__init__(self, parent, -1, self.Figure)
Laurent@1198:         self.SetWindowStyle(wx.WANTS_CHARS)
Laurent@1198:         self.SetBackgroundColour(wx.WHITE)
Laurent@1209:         
Laurent@1209:         # Bind wx events
Laurent@1214:         self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
Laurent@1198:         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
Laurent@1198:         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
Laurent@1198:         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
Laurent@1198:         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
Laurent@1198:         self.Bind(wx.EVT_SIZE, self.OnResize)
Laurent@1198:         
Laurent@1209:         # Set canvas min size
Laurent@1198:         canvas_size = self.GetCanvasMinSize()
Laurent@1198:         self.SetMinSize(canvas_size)
Laurent@1209:         
Laurent@1209:         # Define Viewer drop target
Laurent@1209:         self.SetDropTarget(DebugVariableGraphicDropTarget(self, window))
Laurent@1209:         
Laurent@1209:         # Connect matplotlib events
Laurent@1198:         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
Laurent@1198:         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
Laurent@1198:         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
Laurent@1198:         self.mpl_connect('scroll_event', self.OnCanvasScroll)
Laurent@1198:         
Laurent@1267:         # Add buttons for zooming on current displayed data range
Laurent@1267:         self.Buttons.append(
Laurent@1267:                 GraphButton(0, 0, "fit_graph", self.OnZoomFitButton))
Laurent@1267:         
Laurent@1209:         # Add buttons for changing canvas size with predefined height
Laurent@1209:         for size, bitmap in zip(
Laurent@1209:                 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
Laurent@1209:                 ["minimize_graph", "middle_graph", "maximize_graph"]):
Laurent@1209:             self.Buttons.append(
Laurent@1209:                     GraphButton(0, 0, bitmap, 
Laurent@1209:                                 self.GetOnChangeSizeButton(size)))
Laurent@1209:         
Laurent@1209:         # Add buttons for exporting graph values to clipboard and close graph
Laurent@1209:         for bitmap, callback in [
Laurent@1209:                 ("export_graph_mini", self.OnExportGraphButton),
Laurent@1209:                 ("delete_graph", self.OnCloseButton)]:
Laurent@1199:             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
Laurent@1198:         
Laurent@1209:         # Update graphs elements
Laurent@1198:         self.ResetGraphics()
Laurent@1198:         self.RefreshLabelsPosition(canvas_size.height)
Laurent@1209:     
Laurent@1209:     def AddItem(self, item):
Laurent@1209:         """
Laurent@1209:         Add an item to the list of items displayed by Viewer
Laurent@1209:         @param item: Item to add to the list
Laurent@1209:         """
Laurent@1209:         DebugVariableViewer.AddItem(self, item)
Laurent@1209:         self.ResetGraphics()
Laurent@1209:         
Laurent@1209:     def RemoveItem(self, item):
Laurent@1209:         """
Laurent@1209:         Remove an item from the list of items displayed by Viewer
Laurent@1209:         @param item: Item to remove from the list
Laurent@1209:         """
Laurent@1209:         DebugVariableViewer.RemoveItem(self, item)
Laurent@1209:         
Laurent@1209:         # If list of items is not empty
Laurent@1209:         if not self.ItemsIsEmpty():
Laurent@1209:             # Return to parallel graph if there is only one item
Laurent@1209:             # especially if it's actually orthogonal
Laurent@1209:             if len(self.Items) == 1:
Laurent@1209:                 self.GraphType = GRAPH_PARALLEL
Laurent@1209:             self.ResetGraphics()
Laurent@1209:     
Laurent@1212:     def SetCursorTick(self, cursor_tick):
Laurent@1212:         """
Laurent@1212:         Set cursor tick
Laurent@1212:         @param cursor_tick: Cursor tick
Laurent@1212:         """
Laurent@1212:         self.CursorTick = cursor_tick
Laurent@1212:     
Laurent@1267:     def SetZoomFit(self, zoom_fit):
Laurent@1267:         """
Laurent@1267:         Set flag indicating that zoom fit current displayed data range
Laurent@1267:         @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range)
Laurent@1267:         """
Laurent@1267:         # Flag is different from the actual one 
Laurent@1267:         if zoom_fit != self.ZoomFit:
Laurent@1267:             # Save new flag value
Laurent@1267:             self.ZoomFit = zoom_fit
Laurent@1267:             
Laurent@1267:             # Update button for zoom fit bitmap
Laurent@1267:             self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph")
Laurent@1267:             
Laurent@1267:             # Refresh canvas
Laurent@1267:             self.RefreshViewer()
Laurent@1267:         
Laurent@1209:     def SubscribeAllDataConsumers(self):
Laurent@1209:         """
Laurent@1209:         Function that unsubscribe and remove every item that store values of
Laurent@1209:         a variable that doesn't exist in PLC anymore
Laurent@1209:         """
Laurent@1209:         DebugVariableViewer.SubscribeAllDataConsumers(self)
Laurent@1267:         
Laurent@1267:         # Graph still have data to display
Laurent@1209:         if not self.ItemsIsEmpty():
Laurent@1267:             # Reset flag indicating that zoom fit current displayed data range
Laurent@1267:             self.SetZoomFit(False)
Laurent@1267:             
Laurent@1209:             self.ResetGraphics()
Laurent@1209:     
Laurent@1209:     def Is3DCanvas(self):
Laurent@1209:         """
Laurent@1209:         Return if Viewer is a 3D canvas
Laurent@1209:         @return: True if Viewer is a 3D canvas
Laurent@1209:         """
Laurent@1209:         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
Laurent@1198:     
Laurent@1198:     def GetButtons(self):
Laurent@1209:         """
Laurent@1209:         Return list of buttons defined in Viewer
Laurent@1209:         @return: List of buttons
Laurent@1209:         """
Laurent@1209:         # Add contextual buttons to default buttons
Laurent@1198:         return self.Buttons + self.ContextualButtons
Laurent@1198:     
Laurent@1209:     def PopupContextualButtons(self, item, rect, direction=wx.RIGHT):
Laurent@1209:         """
Laurent@1209:         Show contextual menu for item aside a label of this item defined
Laurent@1209:         by the bounding box of label in figure
Laurent@1209:         @param item: Item for which contextual is shown
Laurent@1209:         @param rect: Bounding box of label aside which drawing menu
Laurent@1209:         @param direction: Direction in which buttons must be drawn
Laurent@1209:         """
Laurent@1209:         # Return immediately if contextual menu for item is already shown
Laurent@1209:         if self.ContextualButtonsItem == item:
Laurent@1209:             return
Laurent@1209:         
Laurent@1209:         # Close already shown contextual menu
Laurent@1209:         self.DismissContextualButtons()
Laurent@1209:         
Laurent@1209:         # Save item for which contextual menu is shown
Laurent@1209:         self.ContextualButtonsItem = item
Laurent@1209:         
Laurent@1209:         # If item variable is forced, add button for release variable to
Laurent@1209:         # contextual menu
Laurent@1209:         if self.ContextualButtonsItem.IsForced():
Laurent@1209:             self.ContextualButtons.append(
Laurent@1209:                 GraphButton(0, 0, "release", self.OnReleaseItemButton))
Laurent@1209:         
Laurent@1209:         # Add other buttons to contextual menu
Laurent@1209:         for bitmap, callback in [
Laurent@1209:                 ("force", self.OnForceItemButton),
Laurent@1209:                 ("export_graph_mini", self.OnExportItemGraphButton),
Laurent@1209:                 ("delete_graph", self.OnRemoveItemButton)]:
Laurent@1209:             self.ContextualButtons.append(
Laurent@1209:                     GraphButton(0, 0, bitmap, callback))
Laurent@1209:         
Laurent@1209:         # If buttons are shown at left side or upper side of rect, positions
Laurent@1209:         # will be set in reverse order
Laurent@1209:         buttons = self.ContextualButtons[:]
Laurent@1209:         if direction in [wx.TOP, wx.LEFT]:
Laurent@1209:              buttons.reverse()
Laurent@1209:              
Laurent@1209:         # Set contextual menu buttons position aside rect depending on
Laurent@1209:         # direction given
Laurent@1209:         offset = 0
Laurent@1209:         for button in buttons:
Laurent@1209:             w, h = button.GetSize()
Laurent@1209:             if direction in [wx.LEFT, wx.RIGHT]:
Laurent@1209:                 x = rect.x + (- w - offset
Laurent@1209:                             if direction == wx.LEFT
Laurent@1209:                             else rect.width + offset)
Laurent@1209:                 y = rect.y + (rect.height - h) / 2
Laurent@1209:                 offset += w
Laurent@1209:             else:
Laurent@1209:                 x = rect.x + (rect.width - w ) / 2
Laurent@1209:                 y = rect.y + (- h - offset
Laurent@1209:                               if direction == wx.TOP
Laurent@1209:                               else rect.height + offset)
Laurent@1209:                 offset += h
Laurent@1209:             button.SetPosition(x, y)
Laurent@1209:             button.Show()
Laurent@1209:         
Laurent@1209:         # Refresh canvas
Laurent@1209:         self.ParentWindow.ForceRefresh()
Laurent@1209:     
Laurent@1209:     def DismissContextualButtons(self):
Laurent@1209:         """
Laurent@1209:         Close current shown contextual menu
Laurent@1209:         """
Laurent@1209:         # Return immediately if no contextual menu is shown
Laurent@1198:         if self.ContextualButtonsItem is None:
Laurent@1209:             return
Laurent@1209:         
Laurent@1209:         # Reset variables corresponding to contextual menu
Laurent@1209:         self.ContextualButtonsItem = None
Laurent@1209:         self.ContextualButtons = []
Laurent@1209:         
Laurent@1209:         # Refresh canvas
Laurent@1209:         self.ParentWindow.ForceRefresh()
Laurent@1198:     
Laurent@1198:     def IsOverContextualButton(self, x, y):
Laurent@1209:         """
Laurent@1209:         Return if point is over one contextual button of Viewer
Laurent@1209:         @param x: X coordinate of point
Laurent@1209:         @param y: Y coordinate of point
Laurent@1209:         @return: contextual button where point is over
Laurent@1209:         """
Laurent@1198:         for button in self.ContextualButtons:
Laurent@1198:             if button.HitTest(x, y):
Laurent@1209:                 return button
Laurent@1209:         return None
Laurent@1209:     
Laurent@1209:     def ExportGraph(self, item=None):
Laurent@1209:         """
Laurent@1209:         Export item(s) data to clipboard in CSV format
Laurent@1209:         @param item: Item from which data to export, all items if None
Laurent@1209:         (default None)
Laurent@1209:         """
Laurent@1209:         self.ParentWindow.CopyDataToClipboard(
Laurent@1209:             [(item, [entry for entry in item.GetData()])
Laurent@1209:              for item in (self.Items 
Laurent@1209:                           if item is None 
Laurent@1209:                           else [item])])
Laurent@1209:     
Laurent@1267:     def OnZoomFitButton(self):
Laurent@1267:         """
Laurent@1267:         Function called when Viewer Zoom Fit button is pressed
Laurent@1267:         """
Laurent@1267:         # Toggle zoom fit flag value
Laurent@1267:         self.SetZoomFit(not self.ZoomFit)
Laurent@1267:         
Laurent@1209:     def GetOnChangeSizeButton(self, height):
Laurent@1209:         """
Laurent@1209:         Function that generate callback function for change Viewer height to
Laurent@1209:         pre-defined height button
Laurent@1209:         @param height: Height that change Viewer to
Laurent@1209:         @return: callback function
Laurent@1209:         """
Laurent@1198:         def OnChangeSizeButton():
Laurent@1264:             self.SetCanvasHeight(height)
Laurent@1198:         return OnChangeSizeButton
Laurent@1198:     
Laurent@1198:     def OnExportGraphButton(self):
Laurent@1209:         """
Laurent@1209:         Function called when Viewer Export button is pressed
Laurent@1209:         """
Laurent@1209:         # Export data of every item in Viewer
Laurent@1198:         self.ExportGraph()
Laurent@1198:     
Laurent@1209:     def OnForceItemButton(self):
Laurent@1209:         """
Laurent@1209:         Function called when contextual menu Force button is pressed
Laurent@1209:         """
Laurent@1209:         # Open dialog for forcing item variable value 
Laurent@1200:         self.ForceValue(self.ContextualButtonsItem)
Laurent@1209:         # Close contextual menu
Laurent@1198:         self.DismissContextualButtons()
Laurent@1198:         
Laurent@1209:     def OnReleaseItemButton(self):
Laurent@1209:         """
Laurent@1209:         Function called when contextual menu Release button is pressed
Laurent@1209:         """
Laurent@1209:         # Release item variable value 
Laurent@1200:         self.ReleaseValue(self.ContextualButtonsItem)
Laurent@1209:         # Close contextual menu
Laurent@1198:         self.DismissContextualButtons()
Laurent@1198:     
Laurent@1198:     def OnExportItemGraphButton(self):
Laurent@1209:         """
Laurent@1209:         Function called when contextual menu Export button is pressed
Laurent@1209:         """
Laurent@1209:         # Export data of item variable
Laurent@1200:         self.ExportGraph(self.ContextualButtonsItem)
Laurent@1209:         # Close contextual menu
Laurent@1198:         self.DismissContextualButtons()
Laurent@1198:         
Laurent@1198:     def OnRemoveItemButton(self):            
Laurent@1209:         """
Laurent@1209:         Function called when contextual menu Remove button is pressed
Laurent@1209:         """
Laurent@1209:         # Remove item from Viewer
Laurent@1198:         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
Laurent@1198:                      self.ContextualButtonsItem)
Laurent@1209:         # Close contextual menu
Laurent@1198:         self.DismissContextualButtons()
Laurent@1198:     
Laurent@1209:     def HandleCursorMove(self, event):
Laurent@1212:         """
Laurent@1212:         Update Cursor position according to mouse position and graph type
Laurent@1212:         @param event: Mouse event
Laurent@1212:         """
Laurent@1209:         start_tick, end_tick = self.ParentWindow.GetRange()
Laurent@1209:         cursor_tick = None
Laurent@1209:         items = self.ItemsDict.values()
Laurent@1212:         
Laurent@1212:         # Graph is orthogonal
Laurent@1209:         if self.GraphType == GRAPH_ORTHOGONAL:
Laurent@1212:             # Extract items data displayed in canvas figure
Laurent@1267:             start_tick = max(start_tick, self.GetItemsMinCommonTick())
Laurent@1267:             end_tick = max(end_tick, start_tick)
Laurent@1209:             x_data = items[0].GetData(start_tick, end_tick)
Laurent@1209:             y_data = items[1].GetData(start_tick, end_tick)
Laurent@1212:             
Laurent@1212:             # Search for the nearest point from mouse position
Laurent@1209:             if len(x_data) > 0 and len(y_data) > 0:
Laurent@1212:                 length = min(len(x_data), len(y_data)) 
Laurent@1212:                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \
Laurent@1212:                                (y_data[:length,1]-event.ydata) ** 2)
Laurent@1212:                 
Laurent@1212:                 # Set cursor tick to the tick of this point
Laurent@1209:                 cursor_tick = x_data[numpy.argmin(d), 0]
Laurent@1212:         
Laurent@1212:         # Graph is parallel
Laurent@1209:         else:
Laurent@1212:             # Extract items tick
Laurent@1209:             data = items[0].GetData(start_tick, end_tick)
Laurent@1212:             
Laurent@1212:             # Search for point that tick is the nearest from mouse X position
Laurent@1212:             # and set cursor tick to the tick of this point
Laurent@1209:             if len(data) > 0:
Laurent@1212:                 cursor_tick = data[numpy.argmin(
Laurent@1212:                         numpy.abs(data[:,0] - event.xdata)), 0]
Laurent@1212:         
Laurent@1212:         # Update cursor tick
Laurent@1209:         if cursor_tick is not None:
Laurent@1209:             self.ParentWindow.SetCursorTick(cursor_tick)
Laurent@1209:     
Laurent@1209:     def OnCanvasButtonPressed(self, event):
Laurent@1209:         """
Laurent@1209:         Function called when a button of mouse is pressed
Laurent@1209:         @param event: Mouse event
Laurent@1209:         """
Laurent@1212:         # Get mouse position, graph Y coordinate is inverted in matplotlib
Laurent@1212:         # comparing to wx
Laurent@1209:         width, height = self.GetSize()
Laurent@1209:         x, y = event.x, height - event.y
Laurent@1209:         
Laurent@1209:         # Return immediately if mouse is over a button
Laurent@1209:         if self.IsOverButton(x, y):
Laurent@1209:             return 
Laurent@1209:         
Laurent@1209:         # Mouse was clicked inside graph figure
Laurent@1209:         if event.inaxes == self.Axes:
Laurent@1209:             
Laurent@1209:             # Find if it was on an item label
Laurent@1209:             item_idx = None
Laurent@1209:             # Check every label paired with corresponding item
Laurent@1209:             for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
Laurent@1209:                          [pair for pair in enumerate(self.Labels)]):
Laurent@1209:                 # Get label bounding box
Laurent@1209:                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
Laurent@1209:                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
Laurent@1209:                 # Check if mouse was over label
Laurent@1209:                 if rect.InsideXY(x, y):
Laurent@1209:                     item_idx = i
Laurent@1209:                     break
Laurent@1209:             
Laurent@1209:             # If an item label have been clicked
Laurent@1209:             if item_idx is not None:
Laurent@1209:                 # Hide buttons and contextual buttons
Laurent@1209:                 self.ShowButtons(False)
Laurent@1209:                 self.DismissContextualButtons()
Laurent@1209:                 
Laurent@1209:                 # Start a drag'n drop from mouse position in wx coordinate of
Laurent@1209:                 # parent
Laurent@1209:                 xw, yw = self.GetPosition()
Laurent@1209:                 self.ParentWindow.StartDragNDrop(self, 
Laurent@1209:                     self.ItemsDict.values()[item_idx], 
Laurent@1209:                     x + xw, y + yw, # Current mouse position
Laurent@1209:                     x + xw, y + yw) # Mouse position when button was clicked
Laurent@1209:             
Laurent@1209:             # Don't handle mouse button if canvas is 3D and let matplotlib do
Laurent@1209:             # the default behavior (rotate 3D axes)
Laurent@1209:             elif not self.Is3DCanvas():
Laurent@1209:                 # Save mouse position when clicked
Laurent@1209:                 self.MouseStartPos = wx.Point(x, y)
Laurent@1209:                 
Laurent@1209:                 # Mouse button was left button, start moving cursor
Laurent@1209:                 if event.button == 1:
Laurent@1209:                     # Save current tick in case a drag'n drop is initiate to
Laurent@1209:                     # restore it
Laurent@1209:                     self.StartCursorTick = self.CursorTick
Laurent@1209:                     
Laurent@1209:                     self.HandleCursorMove(event)
Laurent@1209:                     
Laurent@1209:                 # Mouse button is middle button and graph is parallel, start
Laurent@1209:                 # moving graph along X coordinate (tick)
Laurent@1209:                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
Laurent@1209:                     self.StartCursorTick = self.ParentWindow.GetRange()[0]
Laurent@1209:         
Laurent@1209:         # Mouse was clicked outside graph figure and over resize highlight with
Laurent@1209:         # left button, start resizing Viewer
Laurent@1209:         elif event.button == 1 and event.y <= 5:
Laurent@1209:             self.MouseStartPos = wx.Point(x, y)
Laurent@1209:             self.CanvasStartSize = height
Laurent@1209:     
Laurent@1209:     def OnCanvasButtonReleased(self, event):
Laurent@1209:         """
Laurent@1209:         Function called when a button of mouse is released
Laurent@1209:         @param event: Mouse event
Laurent@1209:         """
Laurent@1209:         # If a drag'n drop is in progress, stop it
Laurent@1209:         if self.ParentWindow.IsDragging():
Laurent@1209:             width, height = self.GetSize()
Laurent@1209:             xw, yw = self.GetPosition()
Laurent@1209:             item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0]
Laurent@1209:             # Give mouse position in wx coordinate of parent
Laurent@1209:             self.ParentWindow.StopDragNDrop(item.GetVariable(),
Laurent@1209:                 xw + event.x, yw + height - event.y)
Laurent@1209:         
Laurent@1209:         else:
Laurent@1209:             # Reset any move in progress
Laurent@1209:             self.MouseStartPos = None
Laurent@1209:             self.CanvasStartSize = None
Laurent@1209:             
Laurent@1209:             # Handle button under mouse if it exist
Laurent@1209:             width, height = self.GetSize()
Laurent@1209:             self.HandleButton(event.x, height - event.y)
Laurent@1209:     
Laurent@1209:     def OnCanvasMotion(self, event):
Laurent@1209:         """
Laurent@1209:         Function called when a button of mouse is moved over Viewer
Laurent@1209:         @param event: Mouse event
Laurent@1209:         """
Laurent@1209:         width, height = self.GetSize()
Laurent@1209:         
Laurent@1209:         # If a drag'n drop is in progress, move canvas dragged
Laurent@1209:         if self.ParentWindow.IsDragging():
Laurent@1209:             xw, yw = self.GetPosition()
Laurent@1209:             # Give mouse position in wx coordinate of parent
Laurent@1209:             self.ParentWindow.MoveDragNDrop(
Laurent@1209:                 xw + event.x, 
Laurent@1209:                 yw + height - event.y)
Laurent@1209:         
Laurent@1209:         # If a Viewer resize is in progress, change Viewer size 
Laurent@1209:         elif event.button == 1 and self.CanvasStartSize is not None:
Laurent@1209:             width, height = self.GetSize()
Laurent@1264:             self.SetCanvasHeight(
Laurent@1209:                 self.CanvasStartSize + height - event.y - self.MouseStartPos.y)
Laurent@1209:         
Laurent@1209:         # If no button is pressed, show or hide contextual buttons or resize
Laurent@1209:         # highlight
Laurent@1209:         elif event.button is None:
Laurent@1209:             # Compute direction for items label according graph type
Laurent@1209:             if self.GraphType == GRAPH_PARALLEL: # Graph is parallel
Laurent@1209:                 directions = [wx.RIGHT] * len(self.AxesLabels) + \
Laurent@1209:                              [wx.LEFT] * len(self.Labels)
Laurent@1209:             elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D
Laurent@1209:                 directions = [wx.RIGHT, wx.TOP,  # Directions for AxesLabels
Laurent@1209:                              wx.LEFT, wx.BOTTOM] # Directions for Labels
Laurent@1209:             else: # Graph is orthogonal in 3D
Laurent@1209:                 directions = [wx.LEFT] * len(self.Labels)
Laurent@1209:             
Laurent@1209:             # Find if mouse is over an item label
Laurent@1209:             item_idx = None
Laurent@1209:             menu_direction = None
Laurent@1209:             for (i, t), dir in zip(
Laurent@1209:                     [pair for pair in enumerate(self.AxesLabels)] + 
Laurent@1209:                     [pair for pair in enumerate(self.Labels)], 
Laurent@1209:                     directions):
Laurent@1209:                 # Check every label paired with corresponding item
Laurent@1209:                 (x0, y0), (x1, y1) = t.get_window_extent().get_points()
Laurent@1209:                 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
Laurent@1209:                 # Check if mouse was over label
Laurent@1209:                 if rect.InsideXY(event.x, height - event.y):
Laurent@1209:                     item_idx = i
Laurent@1209:                     menu_direction = dir
Laurent@1209:                     break
Laurent@1209:             
Laurent@1209:             # If mouse is over an item label, 
Laurent@1209:             if item_idx is not None:
Laurent@1209:                 self.PopupContextualButtons(
Laurent@1209:                     self.ItemsDict.values()[item_idx], 
Laurent@1209:                     rect, menu_direction)
Laurent@1209:                 return
Laurent@1209:             
Laurent@1209:             # If mouse isn't over a contextual menu, hide the current shown one
Laurent@1209:             # if it exists 
Laurent@1209:             if self.IsOverContextualButton(event.x, height - event.y) is None:
Laurent@1209:                 self.DismissContextualButtons()
Laurent@1209:             
Laurent@1209:             # Update resize highlight
Laurent@1209:             if event.y <= 5:
Laurent@1209:                 if self.SetHighlight(HIGHLIGHT_RESIZE):
Laurent@1209:                     self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
Laurent@1209:                     self.ParentWindow.ForceRefresh()
Laurent@1209:             else:
Laurent@1209:                 if self.SetHighlight(HIGHLIGHT_NONE):
Laurent@1209:                     self.SetCursor(wx.NullCursor)
Laurent@1209:                     self.ParentWindow.ForceRefresh()
Laurent@1209:         
Laurent@1209:         # Handle buttons if canvas is not 3D 
Laurent@1209:         elif not self.Is3DCanvas():
Laurent@1209:             
Laurent@1209:             # If left button is pressed
Laurent@1209:             if event.button == 1:
Laurent@1209:                 
Laurent@1209:                 # Mouse is inside graph figure
Laurent@1209:                 if event.inaxes == self.Axes:
Laurent@1209:                     
Laurent@1209:                     # If a cursor move is in progress, update cursor position
Laurent@1209:                     if self.MouseStartPos is not None:
Laurent@1209:                         self.HandleCursorMove(event)
Laurent@1209:                 
Laurent@1209:                 # Mouse is outside graph figure, cursor move is in progress and
Laurent@1209:                 # there is only one item in Viewer, start a drag'n drop
Laurent@1209:                 elif self.MouseStartPos is not None and len(self.Items) == 1:
Laurent@1209:                     xw, yw = self.GetPosition()
Laurent@1209:                     self.ParentWindow.SetCursorTick(self.StartCursorTick)
Laurent@1209:                     self.ParentWindow.StartDragNDrop(self, 
Laurent@1209:                         self.ItemsDict.values()[0],
Laurent@1209:                         # Current mouse position
Laurent@1209:                         event.x + xw, height - event.y + yw,
Laurent@1209:                         # Mouse position when button was clicked
Laurent@1209:                         self.MouseStartPos.x + xw,
Laurent@1209:                         self.MouseStartPos.y + yw)
Laurent@1209:             
Laurent@1209:             # If middle button is pressed and moving graph along X coordinate
Laurent@1209:             # is in progress
Laurent@1209:             elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \
Laurent@1209:                  self.MouseStartPos is not None:
Laurent@1209:                 start_tick, end_tick = self.ParentWindow.GetRange()
Laurent@1209:                 rect = self.GetAxesBoundingBox()
Laurent@1209:                 
Laurent@1209:                 # Move graph along X coordinate
Laurent@1209:                 self.ParentWindow.SetCanvasPosition(
Laurent@1209:                     self.StartCursorTick + 
Laurent@1209:                     (self.MouseStartPos.x - event.x) *
Laurent@1209:                     (end_tick - start_tick) / rect.width)
Laurent@1209:     
Laurent@1209:     def OnCanvasScroll(self, event):
Laurent@1209:         """
Laurent@1209:         Function called when a wheel mouse is use in Viewer
Laurent@1209:         @param event: Mouse event
Laurent@1209:         """
Laurent@1209:         # Change X range of graphs if mouse is in canvas figure and ctrl is
Laurent@1209:         # pressed
Laurent@1209:         if event.inaxes is not None and event.guiEvent.ControlDown():
Laurent@1209:             
Laurent@1209:             # Calculate position of fixed tick point according to graph type
Laurent@1209:             # and mouse position
Laurent@1209:             if self.GraphType == GRAPH_ORTHOGONAL:
Laurent@1209:                 start_tick, end_tick = self.ParentWindow.GetRange()
Laurent@1209:                 tick = (start_tick + end_tick) / 2.
Laurent@1209:             else:
Laurent@1209:                 tick = event.xdata
Laurent@1209:             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
Laurent@1209:             
Laurent@1209:             # Vetoing event to prevent parent panel to be scrolled
Laurent@1209:             self.ParentWindow.VetoScrollEvent = True
Laurent@1209:     
Laurent@1214:     def OnLeftDClick(self, event):
Laurent@1214:         """
Laurent@1214:         Function called when a left mouse button is double clicked
Laurent@1214:         @param event: Mouse event
Laurent@1214:         """
Laurent@1214:         # Check that double click was done inside figure
Laurent@1214:         pos = event.GetPosition()
Laurent@1214:         rect = self.GetAxesBoundingBox()
Laurent@1214:         if rect.InsideXY(pos.x, pos.y):
Laurent@1214:             # Reset Cursor tick to value before starting clicking
Laurent@1214:             self.ParentWindow.SetCursorTick(self.StartCursorTick)
Laurent@1214:             # Toggle to text Viewer(s)
Laurent@1214:             self.ParentWindow.ToggleViewerType(self)
Laurent@1214:         
Laurent@1214:         else:
Laurent@1214:             event.Skip()
Laurent@1214:     
Laurent@1209:     # Cursor tick move for each arrow key
Laurent@1209:     KEY_CURSOR_INCREMENT = {
Laurent@1209:         wx.WXK_LEFT: -1,
Laurent@1209:         wx.WXK_RIGHT: 1,
Laurent@1267:         wx.WXK_UP: 10,
Laurent@1267:         wx.WXK_DOWN: -10}
Laurent@1209:     
Laurent@1209:     def OnKeyDown(self, event):
Laurent@1209:         """
Laurent@1209:         Function called when key is pressed
Laurent@1209:         @param event: wx.KeyEvent
Laurent@1209:         """
Laurent@1209:         # If cursor is shown and arrow key is pressed, move cursor tick
Laurent@1209:         if self.CursorTick is not None:
Laurent@1209:             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
Laurent@1209:             if move is not None:
Laurent@1209:                 self.ParentWindow.MoveCursorTick(move)
Laurent@1209:         event.Skip()
Laurent@1209:     
Laurent@1200:     def OnLeave(self, event):
Laurent@1209:         """
Laurent@1209:         Function called when mouse leave Viewer
Laurent@1209:         @param event: wx.MouseEvent
Laurent@1209:         """
Laurent@1209:         # If Viewer is not resizing, reset resize highlight
Laurent@1209:         if self.CanvasStartSize is None:
Laurent@1209:             self.SetHighlight(HIGHLIGHT_NONE)
Laurent@1209:             self.SetCursor(wx.NullCursor)
Laurent@1200:             DebugVariableViewer.OnLeave(self, event)
Laurent@1200:         else:
Laurent@1200:             event.Skip()
Laurent@1200:     
Laurent@1212:     def GetCanvasMinSize(self):
Laurent@1212:         """
Laurent@1212:         Return the minimum size of Viewer so that all items label can be
Laurent@1212:         displayed
Laurent@1212:         @return: wx.Size containing Viewer minimum size
Laurent@1212:         """
Laurent@1212:         # The minimum height take in account the height of all items, padding
Laurent@1212:         # inside figure and border around figure
Laurent@1212:         return wx.Size(200, 
Laurent@1212:             CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
Laurent@1212:             2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
Laurent@1212:     
Laurent@1264:     def SetCanvasHeight(self, height):
Laurent@1212:         """
Laurent@1212:         Set Viewer size checking that it respects Viewer minimum size
Laurent@1212:         @param height: Viewer height
Laurent@1212:         """
Laurent@1264:         min_width, min_height = self.GetCanvasMinSize()
Laurent@1264:         height = max(height, min_height)
Laurent@1264:         self.SetMinSize(wx.Size(min_width, height))
Laurent@1212:         self.RefreshLabelsPosition(height)
Laurent@1212:         self.ParentWindow.RefreshGraphicsSizer()
Laurent@1212:         
Laurent@1212:     def GetAxesBoundingBox(self, parent_coordinate=False):
Laurent@1212:         """
Laurent@1212:         Return figure bounding box in wx coordinate
Laurent@1212:         @param parent_coordinate: True if use parent coordinate (default False)
Laurent@1212:         """
Laurent@1212:         # Calculate figure bounding box. Y coordinate is inverted in matplotlib
Laurent@1212:         # figure comparing to wx panel
Laurent@1212:         width, height = self.GetSize()
Laurent@1212:         ax, ay, aw, ah = self.figure.gca().get_position().bounds
Laurent@1212:         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
Laurent@1212:                        aw * width + 2, ah * height + 1)
Laurent@1212:         
Laurent@1212:         # If parent_coordinate, add Viewer position in parent
Laurent@1212:         if parent_coordinate:
Laurent@1212:             xw, yw = self.GetPosition()
Laurent@1212:             bbox.x += xw
Laurent@1212:             bbox.y += yw
Laurent@1212:         
Laurent@1212:         return bbox
Laurent@1212:     
Laurent@1212:     def RefreshHighlight(self, x, y):
Laurent@1212:         """
Laurent@1212:         Refresh Viewer highlight according to mouse position
Laurent@1212:         @param x: X coordinate of mouse pointer
Laurent@1212:         @param y: Y coordinate of mouse pointer
Laurent@1212:         """
Laurent@1212:         width, height = self.GetSize()
Laurent@1212:         
Laurent@1212:         # Mouse is over Viewer figure and graph is not 3D
Laurent@1212:         bbox = self.GetAxesBoundingBox()
Laurent@1212:         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
Laurent@1212:             rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
Laurent@1212:             # Mouse is over Viewer left part of figure
Laurent@1212:             if rect.InsideXY(x, y):
Laurent@1212:                 self.SetHighlight(HIGHLIGHT_LEFT)
Laurent@1212:             
Laurent@1212:             # Mouse is over Viewer right part of figure
Laurent@1212:             else:
Laurent@1212:                 self.SetHighlight(HIGHLIGHT_RIGHT)
Laurent@1212:         
Laurent@1212:         # Mouse is over upper part of Viewer
Laurent@1212:         elif y < height / 2:
Laurent@1212:             # Viewer is upper one in Debug Variable Panel, show highlight
Laurent@1212:             if self.ParentWindow.IsViewerFirst(self):
Laurent@1212:                 self.SetHighlight(HIGHLIGHT_BEFORE)
Laurent@1212:             
Laurent@1212:             # Viewer is not the upper one, show highlight in previous one
Laurent@1212:             # It prevents highlight to move when mouse leave one Viewer to
Laurent@1212:             # another
Laurent@1212:             else:
Laurent@1212:                 self.SetHighlight(HIGHLIGHT_NONE)
Laurent@1212:                 self.ParentWindow.HighlightPreviousViewer(self)
Laurent@1212:         
Laurent@1212:         # Mouse is over lower part of Viewer
Laurent@1212:         else:
Laurent@1212:             self.SetHighlight(HIGHLIGHT_AFTER)
Laurent@1212:     
Laurent@1212:     def OnAxesMotion(self, event):
Laurent@1212:         """
Laurent@1212:         Function overriding default function called when mouse is dragged for
Laurent@1212:         rotating graph preventing refresh to be called too quickly
Laurent@1212:         @param event: Mouse event
Laurent@1212:         """
Laurent@1212:         if self.Is3DCanvas():
Laurent@1212:             # Call default function at most 10 times per second
Laurent@1212:             current_time = gettime()
Laurent@1212:             if current_time - self.LastMotionTime > REFRESH_PERIOD:
Laurent@1212:                 self.LastMotionTime = current_time
Laurent@1212:                 Axes3D._on_move(self.Axes, event)
Laurent@1212:     
Laurent@1212:     def GetAddTextFunction(self):
Laurent@1212:         """
Laurent@1212:         Return function for adding text in figure according to graph type
Laurent@1212:         @return: Function adding text to figure
Laurent@1212:         """
Laurent@1212:         text_func = (self.Axes.text2D if self.Is3DCanvas() else self.Axes.text)
Laurent@1212:         def AddText(*args, **kwargs):
Laurent@1212:             args = [0, 0, ""]
Laurent@1212:             kwargs["transform"] = self.Axes.transAxes
Laurent@1212:             return text_func(*args, **kwargs)
Laurent@1212:         return AddText
andrej@1497: 
andrej@1497:     def SetAxesColor(self, color):
andrej@1497:         if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"):
andrej@1497:             self.Axes.set_prop_cycle(cycler('color',color))
andrej@1497:         else:
andrej@1497:             self.Axes.set_color_cycle(color)
andrej@1497:         
Laurent@1212:     def ResetGraphics(self):
Laurent@1212:         """
Laurent@1212:         Reset figure and graphical elements displayed in it
Laurent@1212:         Called any time list of items or graph type change 
Laurent@1212:         """
Laurent@1212:         # Clear figure from any axes defined
Laurent@1212:         self.Figure.clear()
Laurent@1212:         
Laurent@1212:         # Add 3D projection if graph is in 3D
Laurent@1212:         if self.Is3DCanvas():
Laurent@1212:             self.Axes = self.Figure.gca(projection='3d')
andrej@1497:             self.SetAxesColor(['b'])
Laurent@1212:             
Laurent@1212:             # Override function to prevent too much refresh when graph is 
Laurent@1212:             # rotated
Laurent@1212:             self.LastMotionTime = gettime()
Laurent@1212:             setattr(self.Axes, "_on_move", self.OnAxesMotion)
Laurent@1212:             
Laurent@1212:             # Init graph mouse event so that graph can be rotated
Laurent@1212:             self.Axes.mouse_init()
Laurent@1212:             
Laurent@1212:             # Set size of Z axis labels
Laurent@1212:             self.Axes.tick_params(axis='z', labelsize='small')
Laurent@1212:         
Laurent@1212:         else:
Laurent@1212:             self.Axes = self.Figure.gca()
andrej@1497:             self.SetAxesColor(COLOR_CYCLE)
Laurent@1212:         
Laurent@1212:         # Set size of X and Y axis labels
Laurent@1212:         self.Axes.tick_params(axis='x', labelsize='small')
Laurent@1212:         self.Axes.tick_params(axis='y', labelsize='small')
Laurent@1212:         
Laurent@1212:         # Init variables storing graphical elements added to figure
Laurent@1212:         self.Plots = []      # List of curves
Laurent@1212:         self.VLine = None    # Vertical line for cursor
Laurent@1212:         self.HLine = None    # Horizontal line for cursor (only orthogonal 2D)
Laurent@1212:         self.AxesLabels = [] # List of items variable path text label
Laurent@1212:         self.Labels = []     # List of items text label
Laurent@1212:         
Laurent@1212:         # Get function to add a text in figure according to graph type 
Laurent@1212:         add_text_func = self.GetAddTextFunction()
Laurent@1212:         
Laurent@1212:         # Graph type is parallel or orthogonal in 3D
Laurent@1212:         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
Laurent@1212:             num_item = len(self.Items)
Laurent@1212:             for idx in xrange(num_item):
Laurent@1212:                 
Laurent@1212:                 # Get color from color cycle (black if only one item)
Laurent@1212:                 color = ('k' if num_item == 1
Laurent@1212:                              else COLOR_CYCLE[idx % len(COLOR_CYCLE)])
Laurent@1212:                 
Laurent@1212:                 # In 3D graph items variable label are not displayed as text
Laurent@1212:                 # in figure, but as axis title
Laurent@1212:                 if not self.Is3DCanvas():
Laurent@1212:                     # Items variable labels are in figure upper left corner
Laurent@1212:                     self.AxesLabels.append(
Laurent@1212:                         add_text_func(size='small', color=color,
Laurent@1212:                                       verticalalignment='top'))
Laurent@1212:                 
Laurent@1212:                 # Items variable labels are in figure lower right corner
Laurent@1212:                 self.Labels.append(
Laurent@1212:                     add_text_func(size='large', color=color, 
Laurent@1212:                                   horizontalalignment='right'))
Laurent@1212:         
Laurent@1212:         # Graph type is orthogonal in 2D
Laurent@1212:         else:
Laurent@1212:             # X coordinate labels are in figure lower side
Laurent@1212:             self.AxesLabels.append(add_text_func(size='small'))
Laurent@1212:             self.Labels.append(
Laurent@1212:                 add_text_func(size='large',
Laurent@1212:                               horizontalalignment='right'))
Laurent@1212:             
Laurent@1212:             # Y coordinate labels are vertical and in figure left side
Laurent@1212:             self.AxesLabels.append(
Laurent@1264:                 add_text_func(size='small', rotation='vertical',
Laurent@1264:                               verticalalignment='bottom'))
Laurent@1212:             self.Labels.append(
Laurent@1212:                 add_text_func(size='large', rotation='vertical',
Laurent@1212:                               verticalalignment='top'))
Laurent@1212:        
Laurent@1212:         # Refresh position of labels according to Viewer size
Laurent@1212:         width, height = self.GetSize()
Laurent@1212:         self.RefreshLabelsPosition(height)
Laurent@1212:     
Laurent@1198:     def RefreshLabelsPosition(self, height):
Laurent@1212:         """
Laurent@1212:         Function called when mouse leave Viewer
Laurent@1212:         @param event: wx.MouseEvent
Laurent@1212:         """
Laurent@1212:         # Figure position like text position in figure are expressed is ratio
Laurent@1212:         # canvas size and figure size. As we want that border around figure and
Laurent@1212:         # text position in figure don't change when canvas size change, we
Laurent@1212:         # expressed border and text position in pixel on screen and apply the
Laurent@1212:         # ratio calculated hereafter to get border and text position in
Laurent@1214:         # matplotlib coordinate
Laurent@1212:         canvas_ratio = 1. / height # Divide by canvas height in pixel
Laurent@1212:         graph_ratio = 1. / (
Laurent@1212:             (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio)
Laurent@1212:              * height)             # Divide by figure height in pixel
Laurent@1212:         
Laurent@1212:         # Update position of figure (keeping up and bottom border the same
Laurent@1212:         # size)
Laurent@1198:         self.Figure.subplotpars.update(
Laurent@1198:             top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
Laurent@1198:             bottom= CANVAS_BORDER[0] * canvas_ratio)
Laurent@1198:         
Laurent@1212:         # Update position of items labels
Laurent@1198:         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
Laurent@1198:             num_item = len(self.Items)
Laurent@1198:             for idx in xrange(num_item):
Laurent@1212:                 
Laurent@1212:                 # In 3D graph items variable label are not displayed
Laurent@1198:                 if not self.Is3DCanvas():
Laurent@1212:                     # Items variable labels are in figure upper left corner
Laurent@1198:                     self.AxesLabels[idx].set_position(
Laurent@1198:                         (0.05, 
Laurent@1212:                          1.0 - (CANVAS_PADDING + 
Laurent@1212:                                 AXES_LABEL_HEIGHT * idx) * graph_ratio))
Laurent@1212:                 
Laurent@1212:                 # Items variable labels are in figure lower right corner
Laurent@1198:                 self.Labels[idx].set_position(
Laurent@1198:                     (0.95, 
Laurent@1198:                      CANVAS_PADDING * graph_ratio + 
Laurent@1198:                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
Laurent@1198:         else:
Laurent@1212:             # X coordinate labels are in figure lower side
Laurent@1212:             self.AxesLabels[0].set_position(
Laurent@1212:                     (0.1, CANVAS_PADDING * graph_ratio))
Laurent@1212:             self.Labels[0].set_position(
Laurent@1212:                     (0.95, CANVAS_PADDING * graph_ratio))
Laurent@1212:             
Laurent@1212:             # Y coordinate labels are vertical and in figure left side
Laurent@1212:             self.AxesLabels[1].set_position(
Laurent@1212:                     (0.05, 2 * CANVAS_PADDING * graph_ratio))
Laurent@1212:             self.Labels[1].set_position(
Laurent@1212:                     (0.05, 1.0 - CANVAS_PADDING * graph_ratio))
Laurent@1212:         
Laurent@1212:         # Update subplots
Laurent@1198:         self.Figure.subplots_adjust()
Laurent@1198:     
Laurent@1198:     def RefreshViewer(self, refresh_graphics=True):
Laurent@1267:         """
Laurent@1267:         Function called to refresh displayed by matplotlib canvas
Laurent@1267:         @param refresh_graphics: Flag indicating that graphs have to be
Laurent@1267:         refreshed (False: only label values have to be refreshed)
Laurent@1267:         """
Laurent@1267:         # Refresh graphs if needed
Laurent@1198:         if refresh_graphics:
Laurent@1267:             # Get tick range of values to display
Laurent@1198:             start_tick, end_tick = self.ParentWindow.GetRange()
Laurent@1198:             
Laurent@1267:             # Graph is parallel
Laurent@1198:             if self.GraphType == GRAPH_PARALLEL:    
Laurent@1267:                 # Init list of data range for each variable displayed
Laurent@1267:                 ranges = []
Laurent@1267:                 
Laurent@1267:                 # Get data and range for each variable displayed
Laurent@1198:                 for idx, item in enumerate(self.Items):
Laurent@1267:                     data, min_value, max_value = item.GetDataAndValueRange(
Laurent@1267:                                 start_tick, end_tick, not self.ZoomFit)
Laurent@1267:                     
Laurent@1267:                     # Check that data is not empty
Laurent@1198:                     if data is not None:
Laurent@1267:                         # Add variable range to list of variable data range
Laurent@1267:                         ranges.append((min_value, max_value))
Laurent@1198:                         
Laurent@1267:                         # Add plot to canvas if not yet created
Laurent@1198:                         if len(self.Plots) <= idx:
Laurent@1198:                             self.Plots.append(
Laurent@1198:                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
Laurent@1267:                         
Laurent@1267:                         # Set data to already created plot in canvas
Laurent@1198:                         else:
Laurent@1198:                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
Laurent@1267:                 
Laurent@1267:                 # Get X and Y axis ranges
Laurent@1267:                 x_min, x_max = start_tick, end_tick
Laurent@1267:                 y_min, y_max = merge_ranges(ranges)
Laurent@1267:                 
Laurent@1267:                 # Display cursor in canvas if a cursor tick is defined and it is
Laurent@1267:                 # include in values tick range
Laurent@1267:                 if (self.CursorTick is not None and 
Laurent@1267:                     start_tick <= self.CursorTick <= end_tick):
Laurent@1198:                     
Laurent@1267:                     # Define a vertical line to display cursor position if no
Laurent@1267:                     # line is already defined
Laurent@1198:                     if self.VLine is None:
Laurent@1267:                         self.VLine = self.Axes.axvline(self.CursorTick, 
Laurent@1267:                                                        color=CURSOR_COLOR)
Laurent@1267:                     
Laurent@1267:                     # Set value of vertical line if already defined
Laurent@1198:                     else:
Laurent@1198:                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
Laurent@1198:                     self.VLine.set_visible(True)
Laurent@1267:                 
Laurent@1267:                 # Hide vertical line if cursor tick is not defined or reset
Laurent@1267:                 elif self.VLine is not None:
Laurent@1267:                     self.VLine.set_visible(False)
Laurent@1267:             
Laurent@1267:             # Graph is orthogonal
Laurent@929:             else:
Laurent@1267:                 # Update tick range, removing ticks that don't have a value for
Laurent@1267:                 # each variable
Laurent@1267:                 start_tick = max(start_tick, self.GetItemsMinCommonTick())
Laurent@1267:                 end_tick = max(end_tick, start_tick)
Laurent@1200:                 items = self.ItemsDict.values()
Laurent@1267:                 
Laurent@1267:                 # Get data and range for first variable (X coordinate)
Laurent@1267:                 x_data, x_min, x_max = items[0].GetDataAndValueRange(
Laurent@1267:                                         start_tick, end_tick, not self.ZoomFit)
Laurent@1267:                 # Get data and range for second variable (Y coordinate)
Laurent@1267:                 y_data, y_min, y_max = items[1].GetDataAndValueRange(
Laurent@1267:                                         start_tick, end_tick, not self.ZoomFit)
Laurent@1267:                 
Laurent@1267:                 # Normalize X and Y coordinates value range
Laurent@1267:                 x_min, x_max = merge_ranges([(x_min, x_max)])
Laurent@1267:                 y_min, y_max = merge_ranges([(y_min, y_max)])
Laurent@1267:                 
Laurent@1267:                 # Get X and Y coordinates for cursor if cursor tick is defined 
Laurent@1198:                 if self.CursorTick is not None:
Laurent@1267:                     x_cursor, x_forced = items[0].GetValue(
Laurent@1267:                                             self.CursorTick, raw=True)
Laurent@1267:                     y_cursor, y_forced = items[1].GetValue(
Laurent@1267:                                             self.CursorTick, raw=True)
Laurent@1267:                 
Laurent@1267:                 # Get common data length so that each value has an x and y
Laurent@1267:                 # coordinate
Laurent@1267:                 length = (min(len(x_data), len(y_data))
Laurent@1267:                           if x_data is not None and y_data is not None
Laurent@1267:                           else 0)
Laurent@1267:                 
Laurent@1267:                 # Graph is orthogonal 2D 
Laurent@1198:                 if len(self.Items) < 3:
Laurent@1267:                     
Laurent@1267:                     # Check that x and y data are not empty
Laurent@1198:                     if x_data is not None and y_data is not None:
Laurent@1267:                         
Laurent@1267:                         # Add plot to canvas if not yet created
Laurent@1198:                         if len(self.Plots) == 0:
Laurent@1198:                             self.Plots.append(
Laurent@1198:                                 self.Axes.plot(x_data[:, 1][:length], 
Laurent@1198:                                                y_data[:, 1][:length])[0])
Laurent@1267:                         
Laurent@1267:                         # Set data to already created plot in canvas
Laurent@1198:                         else:
Laurent@1198:                             self.Plots[0].set_data(
Laurent@1198:                                 x_data[:, 1][:length], 
Laurent@1198:                                 y_data[:, 1][:length])
Laurent@916:                     
Laurent@1267:                     # Display cursor in canvas if a cursor tick is defined and it is
Laurent@1267:                     # include in values tick range
Laurent@1267:                     if (self.CursorTick is not None and 
Laurent@1267:                         start_tick <= self.CursorTick <= end_tick):
Laurent@1267:                         
Laurent@1267:                         # Define a vertical line to display cursor x coordinate
Laurent@1267:                         # if no line is already defined
Laurent@924:                         if self.VLine is None:
Laurent@1267:                             self.VLine = self.Axes.axvline(x_cursor, 
Laurent@1267:                                                            color=CURSOR_COLOR)
Laurent@1267:                         # Set value of vertical line if already defined
Laurent@924:                         else:
Laurent@1198:                             self.VLine.set_xdata((x_cursor, x_cursor))
Laurent@1267:                         
Laurent@1267:                         
Laurent@1267:                         # Define a horizontal line to display cursor y
Laurent@1267:                         # coordinate if no line is already defined
Laurent@1198:                         if self.HLine is None:
Laurent@1267:                             self.HLine = self.Axes.axhline(y_cursor, 
Laurent@1267:                                                            color=CURSOR_COLOR)
Laurent@1267:                         # Set value of horizontal line if already defined
Laurent@1198:                         else:
Laurent@1198:                             self.HLine.set_ydata((y_cursor, y_cursor))
Laurent@1267:                         
Laurent@924:                         self.VLine.set_visible(True)
Laurent@1198:                         self.HLine.set_visible(True)
Laurent@1267:                     
Laurent@1267:                     # Hide vertical and horizontal line if cursor tick is not
Laurent@1267:                     # defined or reset
Laurent@924:                     else:
Laurent@924:                         if self.VLine is not None:
Laurent@924:                             self.VLine.set_visible(False)
Laurent@1198:                         if self.HLine is not None:
Laurent@1198:                             self.HLine.set_visible(False)
Laurent@1267:                 
Laurent@1267:                 # Graph is orthogonal 3D
Laurent@916:                 else:
Laurent@1267:                     # Remove all plots already defined in 3D canvas
Laurent@1198:                     while len(self.Axes.lines) > 0:
Laurent@1198:                         self.Axes.lines.pop()
Laurent@1267:                     
Laurent@1267:                     # Get data and range for third variable (Z coordinate)
Laurent@1267:                     z_data, z_min, z_max = items[2].GetDataAndValueRange(
Laurent@1267:                                     start_tick, end_tick, not self.ZoomFit)
Laurent@1267:                     
Laurent@1267:                     # Normalize Z coordinate value range
Laurent@1267:                     z_min, z_max = merge_ranges([(z_min, z_max)])
Laurent@1267:                     
Laurent@1267:                     # Check that x, y and z data are not empty
Laurent@1267:                     if (x_data is not None and y_data is not None and 
Laurent@1267:                         z_data is not None):
Laurent@1267:                         
Laurent@1267:                         # Get common data length so that each value has an x, y
Laurent@1267:                         # and z coordinate
Laurent@1198:                         length = min(length, len(z_data))
Laurent@1267:                         
Laurent@1267:                         # Add plot to canvas
Laurent@1198:                         self.Axes.plot(x_data[:, 1][:length],
Laurent@1198:                                        y_data[:, 1][:length],
Laurent@1198:                                        zs = z_data[:, 1][:length])
Laurent@1267:                     
Laurent@1267:                     # Display cursor in canvas if a cursor tick is defined and
Laurent@1267:                     # it is include in values tick range
Laurent@1267:                     if (self.CursorTick is not None and 
Laurent@1267:                         start_tick <= self.CursorTick <= end_tick):
Laurent@1267:                         
Laurent@1267:                         # Get Z coordinate for cursor
Laurent@1267:                         z_cursor, z_forced = items[2].GetValue(
Laurent@1267:                                                 self.CursorTick, raw=True)
Laurent@1267:                         
Laurent@1267:                         # Add 3 lines parallel to x, y and z axis to display
Laurent@1267:                         # cursor position in 3D
Laurent@1198:                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
Laurent@1198:                                        {"ys": numpy.array([y_min, y_max])},
Laurent@1198:                                        {"zs": numpy.array([z_min, z_max])}]:
Laurent@1267:                             for param, value in [
Laurent@1267:                                     ("xs", numpy.array([x_cursor, x_cursor])),
Laurent@1267:                                     ("ys", numpy.array([y_cursor, y_cursor])),
Laurent@1267:                                     ("zs", numpy.array([z_cursor, z_cursor]))]:
Laurent@1198:                                 kwargs.setdefault(param, value)
Laurent@1200:                             kwargs["color"] = CURSOR_COLOR
Laurent@1198:                             self.Axes.plot(**kwargs)
Laurent@1267:                     
Laurent@1267:                     # Set Z axis limits
Laurent@1267:                     self.Axes.set_zlim(z_min, z_max)
Laurent@1267:             
Laurent@1267:             # Set X and Y axis limits
Laurent@1198:             self.Axes.set_xlim(x_min, x_max)
Laurent@1198:             self.Axes.set_ylim(y_min, y_max)
Laurent@1198:         
Laurent@1267:         # Get value and forced flag for each variable displayed in graph
Laurent@1267:         # If cursor tick is not defined get value and flag of last received
Laurent@1267:         # or get value and flag of variable at cursor tick
Laurent@1267:         values, forced = apply(zip, [
Laurent@1267:                 (item.GetValue(self.CursorTick)
Laurent@1267:                  if self.CursorTick is not None
Laurent@1267:                  else (item.GetValue(), item.IsForced()))
Laurent@1267:                 for item in self.Items])
Laurent@1267:         
Laurent@1267:         # Get path of each variable displayed simplified using panel variable
Laurent@1267:         # name mask
Laurent@1267:         labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask()) 
Laurent@1267:                   for item in self.Items]
Laurent@1267:         
Laurent@1267:         # Get style for each variable according to 
Laurent@1198:         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
Laurent@1267:         
Laurent@1267:         # Graph is orthogonal 3D, set variables path as 3D axis label
Laurent@1198:         if self.Is3DCanvas():
Laurent@1198:             for idx, label_func in enumerate([self.Axes.set_xlabel, 
Laurent@1198:                                               self.Axes.set_ylabel,
Laurent@1198:                                               self.Axes.set_zlabel]):
Laurent@1267:                 label_func(labels[idx], fontdict={'size': 'small',
Laurent@1267:                                                   'color': COLOR_CYCLE[idx]})
Laurent@1267:         
Laurent@1267:         # Graph is not orthogonal 3D, set variables path in axes labels
Laurent@1198:         else:
Laurent@1198:             for label, text in zip(self.AxesLabels, labels):
Laurent@1198:                 label.set_text(text)
Laurent@1267:         
Laurent@1267:         # Set value label text and style according to value and forced flag for
Laurent@1267:         # each variable displayed
Laurent@1198:         for label, value, style in zip(self.Labels, values, styles):
Laurent@1198:             label.set_text(value)
Laurent@1198:             label.set_style(style)
Laurent@1198:         
Laurent@1267:         # Refresh figure
Laurent@1198:         self.draw()
Laurent@1198: 
Laurent@1209:     def draw(self, drawDC=None):
Laurent@1209:         """
Laurent@1209:         Render the figure.
Laurent@1209:         """
Laurent@1209:         # Render figure using agg
Laurent@1209:         FigureCanvasAgg.draw(self)
Laurent@1209:         
Laurent@1209:         # Get bitmap of figure rendered
Laurent@1209:         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
andrej@1485:         if wx.VERSION < (3, 0, 0):        
andrej@1485:             self.bitmap.UseAlpha()
Laurent@1209:         
Laurent@1209:         # Create DC for rendering graphics in bitmap
Laurent@1209:         destDC = wx.MemoryDC()
Laurent@1209:         destDC.SelectObject(self.bitmap)
Laurent@1209:         
Laurent@1209:         # Get Graphics Context for DC, for anti-aliased and transparent
Laurent@1209:         # rendering
Laurent@1209:         destGC = wx.GCDC(destDC)
Laurent@1209:         
Laurent@1209:         destGC.BeginDrawing()
Laurent@1209:         
Laurent@1209:         # Get canvas size and figure bounding box in canvas
Laurent@1209:         width, height = self.GetSize()
Laurent@1209:         bbox = self.GetAxesBoundingBox()
Laurent@1209:         
Laurent@1209:         # If highlight to display is resize, draw thick grey line at bottom
Laurent@1209:         # side of canvas 
Laurent@1209:         if self.Highlight == HIGHLIGHT_RESIZE:
Laurent@1209:             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
Laurent@1209:             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
Laurent@1209:             destGC.DrawRectangle(0, height - 5, width, 5)
Laurent@1209:         
Laurent@1209:         # If highlight to display is merging graph, draw 50% transparent blue
Laurent@1209:         # rectangle on left or right part of figure depending on highlight type
Laurent@1209:         elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]:
Laurent@1209:             destGC.SetPen(HIGHLIGHT_DROP_PEN)
Laurent@1209:             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
Laurent@1209:             
Laurent@1209:             x_offset = (bbox.width / 2 
Laurent@1209:                         if self.Highlight == HIGHLIGHT_RIGHT
Laurent@1209:                         else 0)
Laurent@1209:             destGC.DrawRectangle(bbox.x + x_offset, bbox.y, 
Laurent@1209:                                  bbox.width / 2, bbox.height)
Laurent@1209:         
Laurent@1209:         # Draw other Viewer common elements
Laurent@1209:         self.DrawCommonElements(destGC, self.GetButtons())
Laurent@1209:         
Laurent@1209:         destGC.EndDrawing()
Laurent@1209:         
Laurent@1209:         self._isDrawn = True
Laurent@1209:         self.gui_repaint(drawDC=drawDC)
Laurent@1209:     
Laurent@1209: