controls/DebugVariablePanel/DebugVariableGraphicViewer.py
changeset 1200 501cb0bb4c05
parent 1199 fc0e7d80494f
child 1207 fb9799a0c0f7
equal deleted inserted replaced
1199:fc0e7d80494f 1200:501cb0bb4c05
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 #This file is part of PLCOpenEditor, a library implementing an IEC 61131-3 editor
       
     5 #based on the plcopen standard. 
       
     6 #
       
     7 #Copyright (C) 2012: Edouard TISSERANT and Laurent BESSARD
       
     8 #
       
     9 #See COPYING file for copyrights details.
       
    10 #
       
    11 #This library is free software; you can redistribute it and/or
       
    12 #modify it under the terms of the GNU General Public
       
    13 #License as published by the Free Software Foundation; either
       
    14 #version 2.1 of the License, or (at your option) any later version.
       
    15 #
       
    16 #This library is distributed in the hope that it will be useful,
       
    17 #but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    18 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    19 #General Public License for more details.
       
    20 #
       
    21 #You should have received a copy of the GNU General Public
       
    22 #License along with this library; if not, write to the Free Software
       
    23 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    24 
       
    25 from types import TupleType
       
    26 from time import time as gettime
       
    27 import numpy
       
    28 
       
    29 import wx
       
    30 
       
    31 import matplotlib
       
    32 matplotlib.use('WX')
       
    33 import matplotlib.pyplot
       
    34 from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
       
    35 from matplotlib.backends.backend_wxagg import _convert_agg_to_wx_bitmap
       
    36 from matplotlib.backends.backend_agg import FigureCanvasAgg
       
    37 from mpl_toolkits.mplot3d import Axes3D
       
    38 
       
    39 from editors.DebugViewer import REFRESH_PERIOD
       
    40 
       
    41 from DebugVariableItem import DebugVariableItem
       
    42 from DebugVariableViewer import *
       
    43 from GraphButton import GraphButton
       
    44 
       
    45 GRAPH_PARALLEL, GRAPH_ORTHOGONAL = range(2)
       
    46 
       
    47 #CANVAS_SIZE_TYPES
       
    48 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI] = [0, 100, 200]
       
    49 
       
    50 DEFAULT_CANVAS_HEIGHT = 200.
       
    51 CANVAS_BORDER = (20., 10.)
       
    52 CANVAS_PADDING = 8.5
       
    53 VALUE_LABEL_HEIGHT = 17.
       
    54 AXES_LABEL_HEIGHT = 12.75
       
    55 
       
    56 COLOR_CYCLE = ['r', 'b', 'g', 'm', 'y', 'k']
       
    57 CURSOR_COLOR = '#800080'
       
    58 
       
    59 def OrthogonalData(item, start_tick, end_tick):
       
    60     data = item.GetData(start_tick, end_tick)
       
    61     min_value, max_value = item.GetValueRange()
       
    62     if min_value is not None and max_value is not None:
       
    63         center = (min_value + max_value) / 2.
       
    64         range = max(1.0, max_value - min_value)
       
    65     else:
       
    66         center = 0.5
       
    67         range = 1.0
       
    68     return data, center - range * 0.55, center + range * 0.55
       
    69 
       
    70 class DebugVariableDropTarget(wx.TextDropTarget):
       
    71     
       
    72     def __init__(self, parent, window):
       
    73         wx.TextDropTarget.__init__(self)
       
    74         self.ParentControl = parent
       
    75         self.ParentWindow = window
       
    76         
       
    77     def __del__(self):
       
    78         self.ParentControl = None
       
    79         self.ParentWindow = None
       
    80         
       
    81     def OnDragOver(self, x, y, d):
       
    82         self.ParentControl.OnMouseDragging(x, y)
       
    83         return wx.TextDropTarget.OnDragOver(self, x, y, d)
       
    84         
       
    85     def OnDropText(self, x, y, data):
       
    86         message = None
       
    87         try:
       
    88             values = eval(data)
       
    89             if not isinstance(values, TupleType):
       
    90                 raise
       
    91         except:
       
    92             message = _("Invalid value \"%s\" for debug variable")%data
       
    93             values = None
       
    94         
       
    95         if message is not None:
       
    96             wx.CallAfter(self.ShowMessage, message)
       
    97         
       
    98         elif values[1] == "debug":
       
    99             width, height = self.ParentControl.GetSize()
       
   100             target_idx = self.ParentControl.GetIndex()
       
   101             merge_type = GRAPH_PARALLEL
       
   102             if self.ParentControl.Is3DCanvas():
       
   103                 if y > height / 2:
       
   104                     target_idx += 1
       
   105                 if len(values) > 1 and values[2] == "move":
       
   106                     self.ParentWindow.MoveValue(values[0], target_idx)
       
   107                 else:
       
   108                     self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   109                 
       
   110             else:
       
   111                 rect = self.ParentControl.GetAxesBoundingBox()
       
   112                 if rect.InsideXY(x, y):
       
   113                     merge_rect = wx.Rect(rect.x, rect.y, rect.width / 2., rect.height)
       
   114                     if merge_rect.InsideXY(x, y):
       
   115                         merge_type = GRAPH_ORTHOGONAL
       
   116                     wx.CallAfter(self.ParentWindow.MergeGraphs, values[0], target_idx, merge_type, force=True)
       
   117                 else:
       
   118                     if y > height / 2:
       
   119                         target_idx += 1
       
   120                     if len(values) > 2 and values[2] == "move":
       
   121                         self.ParentWindow.MoveValue(values[0], target_idx)
       
   122                     else:
       
   123                         self.ParentWindow.InsertValue(values[0], target_idx, force=True)
       
   124     
       
   125     def OnLeave(self):
       
   126         self.ParentWindow.ResetHighlight()
       
   127         return wx.TextDropTarget.OnLeave(self)
       
   128     
       
   129     def ShowMessage(self, message):
       
   130         dialog = wx.MessageDialog(self.ParentWindow, message, _("Error"), wx.OK|wx.ICON_ERROR)
       
   131         dialog.ShowModal()
       
   132         dialog.Destroy()
       
   133 
       
   134 
       
   135 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas):
       
   136     
       
   137     def __init__(self, parent, window, items, graph_type):
       
   138         DebugVariableViewer.__init__(self, window, items)
       
   139         
       
   140         self.CanvasSize = SIZE_MINI
       
   141         self.GraphType = graph_type
       
   142         self.CursorTick = None
       
   143         self.MouseStartPos = None
       
   144         self.StartCursorTick = None
       
   145         self.CanvasStartSize = None
       
   146         self.ContextualButtons = []
       
   147         self.ContextualButtonsItem = None
       
   148         
       
   149         self.Figure = matplotlib.figure.Figure(facecolor='w')
       
   150         self.Figure.subplotpars.update(top=0.95, left=0.1, bottom=0.1, right=0.95)
       
   151         
       
   152         FigureCanvas.__init__(self, parent, -1, self.Figure)
       
   153         self.SetWindowStyle(wx.WANTS_CHARS)
       
   154         self.SetBackgroundColour(wx.WHITE)
       
   155         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
       
   156         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
       
   157         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
       
   158         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
       
   159         self.Bind(wx.EVT_SIZE, self.OnResize)
       
   160         
       
   161         canvas_size = self.GetCanvasMinSize()
       
   162         self.SetMinSize(canvas_size)
       
   163         self.SetDropTarget(DebugVariableDropTarget(self, window))
       
   164         self.mpl_connect('button_press_event', self.OnCanvasButtonPressed)
       
   165         self.mpl_connect('motion_notify_event', self.OnCanvasMotion)
       
   166         self.mpl_connect('button_release_event', self.OnCanvasButtonReleased)
       
   167         self.mpl_connect('scroll_event', self.OnCanvasScroll)
       
   168         
       
   169         for size, bitmap in zip([SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI],
       
   170                                 ["minimize_graph", "middle_graph", "maximize_graph"]):
       
   171             self.Buttons.append(GraphButton(0, 0, bitmap, self.GetOnChangeSizeButton(size)))
       
   172         for bitmap, callback in [("export_graph_mini", self.OnExportGraphButton),
       
   173                                  ("delete_graph", self.OnCloseButton)]:
       
   174             self.Buttons.append(GraphButton(0, 0, bitmap, callback))
       
   175         
       
   176         self.ResetGraphics()
       
   177         self.RefreshLabelsPosition(canvas_size.height)
       
   178         self.ShowButtons(False)
       
   179     
       
   180     def draw(self, drawDC=None):
       
   181         FigureCanvasAgg.draw(self)
       
   182 
       
   183         self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
       
   184         self.bitmap.UseAlpha() 
       
   185         width, height = self.GetSize()
       
   186         bbox = self.GetAxesBoundingBox()
       
   187         
       
   188         destDC = wx.MemoryDC()
       
   189         destDC.SelectObject(self.bitmap)
       
   190         
       
   191         destGC = wx.GCDC(destDC)
       
   192         
       
   193         destGC.BeginDrawing()
       
   194         if self.Highlight == HIGHLIGHT_RESIZE:
       
   195             destGC.SetPen(HIGHLIGHT_RESIZE_PEN)
       
   196             destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH)
       
   197             destGC.DrawRectangle(0, height - 5, width, 5)
       
   198         else:
       
   199             destGC.SetPen(HIGHLIGHT_DROP_PEN)
       
   200             destGC.SetBrush(HIGHLIGHT_DROP_BRUSH)
       
   201             if self.Highlight == HIGHLIGHT_LEFT:
       
   202                 destGC.DrawRectangle(bbox.x, bbox.y, 
       
   203                                      bbox.width / 2, bbox.height)
       
   204             elif self.Highlight == HIGHLIGHT_RIGHT:
       
   205                 destGC.DrawRectangle(bbox.x + bbox.width / 2, bbox.y, 
       
   206                                      bbox.width / 2, bbox.height)
       
   207         
       
   208         self.DrawCommonElements(destGC, self.GetButtons())
       
   209         
       
   210         destGC.EndDrawing()
       
   211         
       
   212         self._isDrawn = True
       
   213         self.gui_repaint(drawDC=drawDC)
       
   214     
       
   215     def GetButtons(self):
       
   216         return self.Buttons + self.ContextualButtons
       
   217     
       
   218     def PopupContextualButtons(self, item, rect, style=wx.RIGHT):
       
   219         if self.ContextualButtonsItem is not None and item != self.ContextualButtonsItem:
       
   220             self.DismissContextualButtons()
       
   221         
       
   222         if self.ContextualButtonsItem is None:
       
   223             self.ContextualButtonsItem = item
       
   224             
       
   225             if self.ContextualButtonsItem.IsForced():
       
   226                 self.ContextualButtons.append(
       
   227                     GraphButton(0, 0, "release", self.OnReleaseButton))
       
   228             for bitmap, callback in [("force", self.OnForceButton),
       
   229                                      ("export_graph_mini", self.OnExportItemGraphButton),
       
   230                                      ("delete_graph", self.OnRemoveItemButton)]:
       
   231                 self.ContextualButtons.append(GraphButton(0, 0, bitmap, callback))
       
   232             
       
   233             offset = 0
       
   234             buttons = self.ContextualButtons[:]
       
   235             if style in [wx.TOP, wx.LEFT]:
       
   236                  buttons.reverse()
       
   237             for button in buttons:
       
   238                 w, h = button.GetSize()
       
   239                 if style in [wx.LEFT, wx.RIGHT]:
       
   240                     x = rect.x + (- w - offset
       
   241                                 if style == wx.LEFT
       
   242                                 else rect.width + offset)
       
   243                     y = rect.y + (rect.height - h) / 2
       
   244                     offset += w
       
   245                 else:
       
   246                     x = rect.x + (rect.width - w ) / 2
       
   247                     y = rect.y + (- h - offset
       
   248                                   if style == wx.TOP
       
   249                                   else rect.height + offset)
       
   250                     offset += h
       
   251                 button.SetPosition(x, y)
       
   252             self.ParentWindow.ForceRefresh()
       
   253     
       
   254     def DismissContextualButtons(self):
       
   255         if self.ContextualButtonsItem is not None:
       
   256             self.ContextualButtonsItem = None
       
   257             self.ContextualButtons = []
       
   258             self.ParentWindow.ForceRefresh()
       
   259     
       
   260     def IsOverContextualButton(self, x, y):
       
   261         for button in self.ContextualButtons:
       
   262             if button.HitTest(x, y):
       
   263                 return True
       
   264         return False
       
   265     
       
   266     def SetMinSize(self, size):
       
   267         wx.Window.SetMinSize(self, size)
       
   268         wx.CallAfter(self.RefreshButtonsPosition)
       
   269     
       
   270     def GetOnChangeSizeButton(self, size):
       
   271         def OnChangeSizeButton():
       
   272             self.CanvasSize = size
       
   273             self.SetCanvasSize(200, self.CanvasSize)
       
   274         return OnChangeSizeButton
       
   275     
       
   276     def OnExportGraphButton(self):
       
   277         self.ExportGraph()
       
   278     
       
   279     def OnForceButton(self):
       
   280         self.ForceValue(self.ContextualButtonsItem)
       
   281         self.DismissContextualButtons()
       
   282         
       
   283     def OnReleaseButton(self):
       
   284         self.ReleaseValue(self.ContextualButtonsItem)
       
   285         self.DismissContextualButtons()
       
   286     
       
   287     def OnExportItemGraphButton(self):
       
   288         self.ExportGraph(self.ContextualButtonsItem)
       
   289         self.DismissContextualButtons()
       
   290         
       
   291     def OnRemoveItemButton(self):            
       
   292         wx.CallAfter(self.ParentWindow.DeleteValue, self, 
       
   293                      self.ContextualButtonsItem)
       
   294         self.DismissContextualButtons()
       
   295     
       
   296     def OnLeave(self, event):
       
   297         if self.Highlight != HIGHLIGHT_RESIZE or self.CanvasStartSize is None:
       
   298             DebugVariableViewer.OnLeave(self, event)
       
   299         else:
       
   300             event.Skip()
       
   301     
       
   302     def RefreshLabelsPosition(self, height):
       
   303         canvas_ratio = 1. / height
       
   304         graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height)
       
   305         
       
   306         self.Figure.subplotpars.update(
       
   307             top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, 
       
   308             bottom= CANVAS_BORDER[0] * canvas_ratio)
       
   309         
       
   310         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
       
   311             num_item = len(self.Items)
       
   312             for idx in xrange(num_item):
       
   313                 if not self.Is3DCanvas():
       
   314                     self.AxesLabels[idx].set_position(
       
   315                         (0.05, 
       
   316                          1.0 - (CANVAS_PADDING + AXES_LABEL_HEIGHT * idx) * graph_ratio))
       
   317                 self.Labels[idx].set_position(
       
   318                     (0.95, 
       
   319                      CANVAS_PADDING * graph_ratio + 
       
   320                      (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio))
       
   321         else:
       
   322             self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio))
       
   323             self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio))
       
   324             self.AxesLabels[1].set_position((0.05, 2 * CANVAS_PADDING * graph_ratio))
       
   325             self.Labels[1].set_position((0.05, 1.0 - CANVAS_PADDING * graph_ratio))
       
   326     
       
   327         self.Figure.subplots_adjust()
       
   328     
       
   329     def GetCanvasMinSize(self):
       
   330         return wx.Size(200, 
       
   331                        CANVAS_BORDER[0] + CANVAS_BORDER[1] + 
       
   332                        2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items))
       
   333     
       
   334     def SetCanvasSize(self, width, height):
       
   335         height = max(height, self.GetCanvasMinSize()[1])
       
   336         self.SetMinSize(wx.Size(width, height))
       
   337         self.RefreshLabelsPosition(height)
       
   338         self.RefreshButtonsPosition()
       
   339         self.ParentWindow.RefreshGraphicsSizer()
       
   340         
       
   341     def GetAxesBoundingBox(self, absolute=False):
       
   342         width, height = self.GetSize()
       
   343         ax, ay, aw, ah = self.figure.gca().get_position().bounds
       
   344         bbox = wx.Rect(ax * width, height - (ay + ah) * height - 1,
       
   345                        aw * width + 2, ah * height + 1)
       
   346         if absolute:
       
   347             xw, yw = self.GetPosition()
       
   348             bbox.x += xw
       
   349             bbox.y += yw
       
   350         return bbox
       
   351     
       
   352     def OnCanvasButtonPressed(self, event):
       
   353         width, height = self.GetSize()
       
   354         x, y = event.x, height - event.y
       
   355         if not self.IsOverButton(x, y):
       
   356             if event.inaxes == self.Axes:
       
   357                 item_idx = None
       
   358                 for i, t in ([pair for pair in enumerate(self.AxesLabels)] + 
       
   359                              [pair for pair in enumerate(self.Labels)]):
       
   360                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   361                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   362                     if rect.InsideXY(x, y):
       
   363                         item_idx = i
       
   364                         break
       
   365                 if item_idx is not None:
       
   366                     self.ShowButtons(False)
       
   367                     self.DismissContextualButtons()
       
   368                     xw, yw = self.GetPosition()
       
   369                     self.ParentWindow.StartDragNDrop(self, 
       
   370                         self.ItemsDict.values()[item_idx], x + xw, y + yw, x + xw, y + yw)
       
   371                 elif not self.Is3DCanvas():
       
   372                     self.MouseStartPos = wx.Point(x, y)
       
   373                     if event.button == 1 and event.inaxes == self.Axes:
       
   374                         self.StartCursorTick = self.CursorTick
       
   375                         self.HandleCursorMove(event)
       
   376                     elif event.button == 2 and self.GraphType == GRAPH_PARALLEL:
       
   377                         width, height = self.GetSize()
       
   378                         start_tick, end_tick = self.ParentWindow.GetRange()
       
   379                         self.StartCursorTick = start_tick
       
   380             
       
   381             elif event.button == 1 and event.y <= 5:
       
   382                 self.MouseStartPos = wx.Point(x, y)
       
   383                 self.CanvasStartSize = self.GetSize()
       
   384     
       
   385     def OnCanvasButtonReleased(self, event):
       
   386         if self.ParentWindow.IsDragging():
       
   387             width, height = self.GetSize()
       
   388             xw, yw = self.GetPosition()
       
   389             self.ParentWindow.StopDragNDrop(
       
   390                 self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0].GetVariable(),
       
   391                 xw + event.x, 
       
   392                 yw + height - event.y)
       
   393         else:
       
   394             self.MouseStartPos = None
       
   395             self.StartCursorTick = None
       
   396             self.CanvasStartSize = None
       
   397             width, height = self.GetSize()
       
   398             self.HandleButton(event.x, height - event.y)
       
   399             if event.y > 5 and self.SetHighlight(HIGHLIGHT_NONE):
       
   400                 self.SetCursor(wx.NullCursor)
       
   401                 self.ParentWindow.ForceRefresh()
       
   402     
       
   403     def OnCanvasMotion(self, event):
       
   404         width, height = self.GetSize()
       
   405         if self.ParentWindow.IsDragging():
       
   406             xw, yw = self.GetPosition()
       
   407             self.ParentWindow.MoveDragNDrop(
       
   408                 xw + event.x, 
       
   409                 yw + height - event.y)
       
   410         else:
       
   411             if not self.Is3DCanvas():
       
   412                 if event.button == 1 and self.CanvasStartSize is None:
       
   413                     if event.inaxes == self.Axes:
       
   414                         if self.MouseStartPos is not None:
       
   415                             self.HandleCursorMove(event)
       
   416                     elif self.MouseStartPos is not None and len(self.Items) == 1:
       
   417                         xw, yw = self.GetPosition()
       
   418                         self.ParentWindow.SetCursorTick(self.StartCursorTick)
       
   419                         self.ParentWindow.StartDragNDrop(self, 
       
   420                             self.ItemsDict.values()[0],
       
   421                             event.x + xw, height - event.y + yw, 
       
   422                             self.MouseStartPos.x + xw, self.MouseStartPos.y + yw)
       
   423                 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and self.MouseStartPos is not None:
       
   424                     start_tick, end_tick = self.ParentWindow.GetRange()
       
   425                     rect = self.GetAxesBoundingBox()
       
   426                     self.ParentWindow.SetCanvasPosition(
       
   427                         self.StartCursorTick + (self.MouseStartPos.x - event.x) *
       
   428                         (end_tick - start_tick) / rect.width)    
       
   429             
       
   430             if event.button == 1 and self.CanvasStartSize is not None:
       
   431                 width, height = self.GetSize()
       
   432                 self.SetCanvasSize(width, 
       
   433                     self.CanvasStartSize.height + height - event.y - self.MouseStartPos.y)
       
   434                 
       
   435             elif event.button in [None, "up", "down"]:
       
   436                 if self.GraphType == GRAPH_PARALLEL:
       
   437                     orientation = [wx.RIGHT] * len(self.AxesLabels) + [wx.LEFT] * len(self.Labels)
       
   438                 elif len(self.AxesLabels) > 0:
       
   439                     orientation = [wx.RIGHT, wx.TOP, wx.LEFT, wx.BOTTOM]
       
   440                 else:
       
   441                     orientation = [wx.LEFT] * len(self.Labels)
       
   442                 item_idx = None
       
   443                 item_style = None
       
   444                 for (i, t), style in zip([pair for pair in enumerate(self.AxesLabels)] + 
       
   445                                          [pair for pair in enumerate(self.Labels)], 
       
   446                                          orientation):
       
   447                     (x0, y0), (x1, y1) = t.get_window_extent().get_points()
       
   448                     rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0)
       
   449                     if rect.InsideXY(event.x, height - event.y):
       
   450                         item_idx = i
       
   451                         item_style = style
       
   452                         break
       
   453                 if item_idx is not None:
       
   454                     self.PopupContextualButtons(self.ItemsDict.values()[item_idx], rect, item_style)
       
   455                     return 
       
   456                 if not self.IsOverContextualButton(event.x, height - event.y):
       
   457                     self.DismissContextualButtons()
       
   458                 
       
   459                 if event.y <= 5:
       
   460                     if self.SetHighlight(HIGHLIGHT_RESIZE):
       
   461                         self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS))
       
   462                         self.ParentWindow.ForceRefresh()
       
   463                 else:
       
   464                     if self.SetHighlight(HIGHLIGHT_NONE):
       
   465                         self.SetCursor(wx.NullCursor)
       
   466                         self.ParentWindow.ForceRefresh()
       
   467     
       
   468     def OnCanvasScroll(self, event):
       
   469         if event.inaxes is not None and event.guiEvent.ControlDown():
       
   470             if self.GraphType == GRAPH_ORTHOGONAL:
       
   471                 start_tick, end_tick = self.ParentWindow.GetRange()
       
   472                 tick = (start_tick + end_tick) / 2.
       
   473             else:
       
   474                 tick = event.xdata
       
   475             self.ParentWindow.ChangeRange(int(-event.step) / 3, tick)
       
   476             self.ParentWindow.VetoScrollEvent = True
       
   477     
       
   478     def RefreshHighlight(self, x, y):
       
   479         width, height = self.GetSize()
       
   480         bbox = self.GetAxesBoundingBox()
       
   481         if bbox.InsideXY(x, y) and not self.Is3DCanvas():
       
   482             rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height)
       
   483             if rect.InsideXY(x, y):
       
   484                 self.SetHighlight(HIGHLIGHT_LEFT)
       
   485             else:
       
   486                 self.SetHighlight(HIGHLIGHT_RIGHT)
       
   487         elif y < height / 2:
       
   488             if self.ParentWindow.IsViewerFirst(self):
       
   489                 self.SetHighlight(HIGHLIGHT_BEFORE)
       
   490             else:
       
   491                 self.SetHighlight(HIGHLIGHT_NONE)
       
   492                 self.ParentWindow.HighlightPreviousViewer(self)
       
   493         else:
       
   494             self.SetHighlight(HIGHLIGHT_AFTER)
       
   495     
       
   496     def OnLeave(self, event):
       
   497         if self.CanvasStartSize is None and self.SetHighlight(HIGHLIGHT_NONE):
       
   498             self.SetCursor(wx.NullCursor)
       
   499             self.ParentWindow.ForceRefresh()
       
   500         DebugVariableViewer.OnLeave(self, event)
       
   501     
       
   502     KEY_CURSOR_INCREMENT = {
       
   503         wx.WXK_LEFT: -1,
       
   504         wx.WXK_RIGHT: 1,
       
   505         wx.WXK_UP: -10,
       
   506         wx.WXK_DOWN: 10}
       
   507     def OnKeyDown(self, event):
       
   508         if self.CursorTick is not None:
       
   509             move = self.KEY_CURSOR_INCREMENT.get(event.GetKeyCode(), None)
       
   510             if move is not None:
       
   511                 self.ParentWindow.MoveCursorTick(move)
       
   512         event.Skip()
       
   513     
       
   514     def HandleCursorMove(self, event):
       
   515         start_tick, end_tick = self.ParentWindow.GetRange()
       
   516         cursor_tick = None
       
   517         items = self.ItemsDict.values()
       
   518         if self.GraphType == GRAPH_ORTHOGONAL:
       
   519             x_data = items[0].GetData(start_tick, end_tick)
       
   520             y_data = items[1].GetData(start_tick, end_tick)
       
   521             if len(x_data) > 0 and len(y_data) > 0:
       
   522                 length = min(len(x_data), len(y_data))
       
   523                 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2)
       
   524                 cursor_tick = x_data[numpy.argmin(d), 0]
       
   525         else:
       
   526             data = items[0].GetData(start_tick, end_tick)
       
   527             if len(data) > 0:
       
   528                 cursor_tick = data[numpy.argmin(numpy.abs(data[:,0] - event.xdata)), 0]
       
   529         if cursor_tick is not None:
       
   530             self.ParentWindow.SetCursorTick(cursor_tick)
       
   531     
       
   532     def OnAxesMotion(self, event):
       
   533         if self.Is3DCanvas():
       
   534             current_time = gettime()
       
   535             if current_time - self.LastMotionTime > REFRESH_PERIOD:
       
   536                 self.LastMotionTime = current_time
       
   537                 Axes3D._on_move(self.Axes, event)
       
   538     
       
   539     def ResetGraphics(self):
       
   540         self.Figure.clear()
       
   541         if self.Is3DCanvas():
       
   542             self.Axes = self.Figure.gca(projection='3d')
       
   543             self.Axes.set_color_cycle(['b'])
       
   544             self.LastMotionTime = gettime()
       
   545             setattr(self.Axes, "_on_move", self.OnAxesMotion)
       
   546             self.Axes.mouse_init()
       
   547             self.Axes.tick_params(axis='z', labelsize='small')
       
   548         else:
       
   549             self.Axes = self.Figure.gca()
       
   550             self.Axes.set_color_cycle(COLOR_CYCLE)
       
   551         self.Axes.tick_params(axis='x', labelsize='small')
       
   552         self.Axes.tick_params(axis='y', labelsize='small')
       
   553         self.Plots = []
       
   554         self.VLine = None
       
   555         self.HLine = None
       
   556         self.Labels = []
       
   557         self.AxesLabels = []
       
   558         if not self.Is3DCanvas():
       
   559             text_func = self.Axes.text
       
   560         else:
       
   561             text_func = self.Axes.text2D
       
   562         if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas():
       
   563             num_item = len(self.Items)
       
   564             for idx in xrange(num_item):
       
   565                 if num_item == 1:
       
   566                     color = 'k'
       
   567                 else:
       
   568                     color = COLOR_CYCLE[idx % len(COLOR_CYCLE)]
       
   569                 if not self.Is3DCanvas():
       
   570                     self.AxesLabels.append(
       
   571                         text_func(0, 0, "", size='small',
       
   572                                   verticalalignment='top', 
       
   573                                   color=color,
       
   574                                   transform=self.Axes.transAxes))
       
   575                 self.Labels.append(
       
   576                     text_func(0, 0, "", size='large', 
       
   577                               horizontalalignment='right',
       
   578                               color=color,
       
   579                               transform=self.Axes.transAxes))
       
   580         else:
       
   581             self.AxesLabels.append(
       
   582                 self.Axes.text(0, 0, "", size='small',
       
   583                                transform=self.Axes.transAxes))
       
   584             self.Labels.append(
       
   585                 self.Axes.text(0, 0, "", size='large',
       
   586                                horizontalalignment='right',
       
   587                                transform=self.Axes.transAxes))
       
   588             self.AxesLabels.append(
       
   589                 self.Axes.text(0, 0, "", size='small',
       
   590                                rotation='vertical',
       
   591                                verticalalignment='bottom',
       
   592                                transform=self.Axes.transAxes))
       
   593             self.Labels.append(
       
   594                 self.Axes.text(0, 0, "", size='large',
       
   595                                rotation='vertical',
       
   596                                verticalalignment='top',
       
   597                                transform=self.Axes.transAxes))
       
   598         width, height = self.GetSize()
       
   599         self.RefreshLabelsPosition(height)
       
   600         
       
   601     def AddItem(self, item):
       
   602         DebugVariableViewer.AddItem(self, item)
       
   603         self.ResetGraphics()
       
   604         
       
   605     def RemoveItem(self, item):
       
   606         DebugVariableViewer.RemoveItem(self, item)
       
   607         if not self.ItemsIsEmpty():
       
   608             if len(self.Items) == 1:
       
   609                 self.GraphType = GRAPH_PARALLEL
       
   610             self.ResetGraphics()
       
   611     
       
   612     def UnsubscribeObsoleteData(self):
       
   613         DebugVariableViewer.UnsubscribeObsoleteData(self)
       
   614         if not self.ItemsIsEmpty():
       
   615             self.ResetGraphics()
       
   616     
       
   617     def Is3DCanvas(self):
       
   618         return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3
       
   619     
       
   620     def SetCursorTick(self, cursor_tick):
       
   621         self.CursorTick = cursor_tick
       
   622         
       
   623     def RefreshViewer(self, refresh_graphics=True):
       
   624         
       
   625         if refresh_graphics:
       
   626             start_tick, end_tick = self.ParentWindow.GetRange()
       
   627             
       
   628             if self.GraphType == GRAPH_PARALLEL:    
       
   629                 min_value = max_value = None
       
   630                 
       
   631                 for idx, item in enumerate(self.Items):
       
   632                     data = item.GetData(start_tick, end_tick)
       
   633                     if data is not None:
       
   634                         item_min_value, item_max_value = item.GetValueRange()
       
   635                         if min_value is None:
       
   636                             min_value = item_min_value
       
   637                         elif item_min_value is not None:
       
   638                             min_value = min(min_value, item_min_value)
       
   639                         if max_value is None:
       
   640                             max_value = item_max_value
       
   641                         elif item_max_value is not None:
       
   642                             max_value = max(max_value, item_max_value)
       
   643                         
       
   644                         if len(self.Plots) <= idx:
       
   645                             self.Plots.append(
       
   646                                 self.Axes.plot(data[:, 0], data[:, 1])[0])
       
   647                         else:
       
   648                             self.Plots[idx].set_data(data[:, 0], data[:, 1])
       
   649                     
       
   650                 if min_value is not None and max_value is not None:
       
   651                     y_center = (min_value + max_value) / 2.
       
   652                     y_range = max(1.0, max_value - min_value)
       
   653                 else:
       
   654                     y_center = 0.5
       
   655                     y_range = 1.0
       
   656                 x_min, x_max = start_tick, end_tick
       
   657                 y_min, y_max = y_center - y_range * 0.55, y_center + y_range * 0.55
       
   658                 
       
   659                 if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
   660                     if self.VLine is None:
       
   661                         self.VLine = self.Axes.axvline(self.CursorTick, color=CURSOR_COLOR)
       
   662                     else:
       
   663                         self.VLine.set_xdata((self.CursorTick, self.CursorTick))
       
   664                     self.VLine.set_visible(True)
       
   665                 else:
       
   666                     if self.VLine is not None:
       
   667                         self.VLine.set_visible(False)
       
   668             else:
       
   669                 min_start_tick = reduce(max, [item.GetData()[0, 0] 
       
   670                                               for item in self.Items
       
   671                                               if len(item.GetData()) > 0], 0)
       
   672                 start_tick = max(start_tick, min_start_tick)
       
   673                 end_tick = max(end_tick, min_start_tick)
       
   674                 items = self.ItemsDict.values()
       
   675                 x_data, x_min, x_max = OrthogonalData(items[0], start_tick, end_tick)
       
   676                 y_data, y_min, y_max = OrthogonalData(items[1], start_tick, end_tick)
       
   677                 if self.CursorTick is not None:
       
   678                     x_cursor, x_forced = items[0].GetValue(self.CursorTick, raw=True)
       
   679                     y_cursor, y_forced = items[1].GetValue(self.CursorTick, raw=True)
       
   680                 length = 0
       
   681                 if x_data is not None and y_data is not None:  
       
   682                     length = min(len(x_data), len(y_data))
       
   683                 if len(self.Items) < 3:
       
   684                     if x_data is not None and y_data is not None:
       
   685                         if len(self.Plots) == 0:
       
   686                             self.Plots.append(
       
   687                                 self.Axes.plot(x_data[:, 1][:length], 
       
   688                                                y_data[:, 1][:length])[0])
       
   689                         else:
       
   690                             self.Plots[0].set_data(
       
   691                                 x_data[:, 1][:length], 
       
   692                                 y_data[:, 1][:length])
       
   693                     
       
   694                     if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
   695                         if self.VLine is None:
       
   696                             self.VLine = self.Axes.axvline(x_cursor, color=CURSOR_COLOR)
       
   697                         else:
       
   698                             self.VLine.set_xdata((x_cursor, x_cursor))
       
   699                         if self.HLine is None:
       
   700                             self.HLine = self.Axes.axhline(y_cursor, color=CURSOR_COLOR)
       
   701                         else:
       
   702                             self.HLine.set_ydata((y_cursor, y_cursor))
       
   703                         self.VLine.set_visible(True)
       
   704                         self.HLine.set_visible(True)
       
   705                     else:
       
   706                         if self.VLine is not None:
       
   707                             self.VLine.set_visible(False)
       
   708                         if self.HLine is not None:
       
   709                             self.HLine.set_visible(False)
       
   710                 else:
       
   711                     while len(self.Axes.lines) > 0:
       
   712                         self.Axes.lines.pop()
       
   713                     z_data, z_min, z_max = OrthogonalData(items[2], start_tick, end_tick)
       
   714                     if self.CursorTick is not None:
       
   715                         z_cursor, z_forced = items[2].GetValue(self.CursorTick, raw=True)
       
   716                     if x_data is not None and y_data is not None and z_data is not None:
       
   717                         length = min(length, len(z_data))
       
   718                         self.Axes.plot(x_data[:, 1][:length],
       
   719                                        y_data[:, 1][:length],
       
   720                                        zs = z_data[:, 1][:length])
       
   721                     self.Axes.set_zlim(z_min, z_max)
       
   722                     if self.CursorTick is not None and start_tick <= self.CursorTick <= end_tick:
       
   723                         for kwargs in [{"xs": numpy.array([x_min, x_max])},
       
   724                                        {"ys": numpy.array([y_min, y_max])},
       
   725                                        {"zs": numpy.array([z_min, z_max])}]:
       
   726                             for param, value in [("xs", numpy.array([x_cursor, x_cursor])),
       
   727                                                  ("ys", numpy.array([y_cursor, y_cursor])),
       
   728                                                  ("zs", numpy.array([z_cursor, z_cursor]))]:
       
   729                                 kwargs.setdefault(param, value)
       
   730                             kwargs["color"] = CURSOR_COLOR
       
   731                             self.Axes.plot(**kwargs)
       
   732                 
       
   733             self.Axes.set_xlim(x_min, x_max)
       
   734             self.Axes.set_ylim(y_min, y_max)
       
   735         
       
   736         variable_name_mask = self.ParentWindow.GetVariableNameMask()
       
   737         if self.CursorTick is not None:
       
   738             values, forced = apply(zip, [item.GetValue(self.CursorTick) for item in self.Items])
       
   739         else:
       
   740             values, forced = apply(zip, [(item.GetValue(), item.IsForced()) for item in self.Items])
       
   741         labels = [item.GetVariable(variable_name_mask) for item in self.Items]
       
   742         styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced)
       
   743         if self.Is3DCanvas():
       
   744             for idx, label_func in enumerate([self.Axes.set_xlabel, 
       
   745                                               self.Axes.set_ylabel,
       
   746                                               self.Axes.set_zlabel]):
       
   747                 label_func(labels[idx], fontdict={'size': 'small','color': COLOR_CYCLE[idx]})
       
   748         else:
       
   749             for label, text in zip(self.AxesLabels, labels):
       
   750                 label.set_text(text)
       
   751         for label, value, style in zip(self.Labels, values, styles):
       
   752             label.set_text(value)
       
   753             label.set_style(style)
       
   754         
       
   755         self.draw()
       
   756 
       
   757     def ExportGraph(self, item=None):
       
   758         if item is not None:
       
   759             variables = [(item, [entry for entry in item.GetData()])]
       
   760         else:
       
   761             variables = [(item, [entry for entry in item.GetData()])
       
   762                          for item in self.Items]
       
   763         self.ParentWindow.CopyDataToClipboard(variables)