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