# HG changeset patch # User Laurent Bessard # Date 1370250706 -7200 # Node ID b351d3a7917cf4c17598505d13ad3fd3a2e23556 # Parent e3f805ba74ca781939d1bdfa634917ac23ab6f52 Rewrite DebugVariablePanel diff -r e3f805ba74ca -r b351d3a7917c controls/DebugVariablePanel/DebugVariableGraphicPanel.py --- a/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Mon Jun 03 08:41:10 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableGraphicPanel.py Mon Jun 03 11:11:46 2013 +0200 @@ -308,7 +308,7 @@ self.DraggingAxesPanel.SetPosition(wx.Point(0, -height)) else: self.DraggingAxesPanel = panel - self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(absolute=True) + self.DraggingAxesBoundingBox = panel.GetAxesBoundingBox(parent_coordinate=True) self.DraggingAxesMousePos = wx.Point( x_mouse_start - self.DraggingAxesBoundingBox.x, y_mouse_start - self.DraggingAxesBoundingBox.y) diff -r e3f805ba74ca -r b351d3a7917c controls/DebugVariablePanel/DebugVariableGraphicViewer.py --- a/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Mon Jun 03 08:41:10 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableGraphicViewer.py Mon Jun 03 11:11:46 2013 +0200 @@ -299,6 +299,13 @@ self.GraphType = GRAPH_PARALLEL self.ResetGraphics() + def SetCursorTick(self, cursor_tick): + """ + Set cursor tick + @param cursor_tick: Cursor tick + """ + self.CursorTick = cursor_tick + def SubscribeAllDataConsumers(self): """ Function that unsubscribe and remove every item that store values of @@ -479,20 +486,44 @@ self.DismissContextualButtons() def HandleCursorMove(self, event): + """ + Update Cursor position according to mouse position and graph type + @param event: Mouse event + """ 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 + min_start_tick = max(start_tick, self.GetItemsMinCommonTick()) + start_tick = max(start_tick, min_start_tick) + end_tick = max(end_tick, min_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)) - d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + (y_data[:length,1]-event.ydata) ** 2) + 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] + 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) @@ -501,8 +532,8 @@ Function called when a button of mouse is pressed @param event: Mouse event """ - # Get mouse position, graph coordinates are inverted comparing to wx - # coordinates + # Get mouse position, graph Y coordinate is inverted in matplotlib + # comparing to wx width, height = self.GetSize() x, y = event.x, height - event.y @@ -721,19 +752,6 @@ # Vetoing event to prevent parent panel to be scrolled self.ParentWindow.VetoScrollEvent = True - def OnAxesMotion(self, event): - """ - Function overriding default function called when mouse is dragged for - rotating graph preventing refresh to be called too quickly - @param event: Mouse event - """ - if self.Is3DCanvas(): - # Call default function at most 10 times per second - current_time = gettime() - if current_time - self.LastMotionTime > REFRESH_PERIOD: - self.LastMotionTime = current_time - Axes3D._on_move(self.Axes, event) - # Cursor tick move for each arrow key KEY_CURSOR_INCREMENT = { wx.WXK_LEFT: -1, @@ -766,138 +784,250 @@ else: event.Skip() + def GetCanvasMinSize(self): + """ + Return the minimum size of Viewer so that all items label can be + displayed + @return: wx.Size containing Viewer minimum size + """ + # 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] + + 2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items)) + + def SetCanvasSize(self, width, height): + """ + Set Viewer size checking that it respects Viewer minimum size + @param width: Viewer width + @param height: Viewer height + """ + height = max(height, self.GetCanvasMinSize()[1]) + self.SetMinSize(wx.Size(width, height)) + self.RefreshLabelsPosition(height) + self.ParentWindow.RefreshGraphicsSizer() + + def GetAxesBoundingBox(self, parent_coordinate=False): + """ + Return figure bounding box in wx coordinate + @param parent_coordinate: True if use parent coordinate (default False) + """ + # Calculate figure bounding box. Y coordinate is inverted in matplotlib + # figure comparing to wx panel + width, height = self.GetSize() + 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 + @param x: X coordinate of mouse pointer + @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(): + rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height) + # 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 + rotating graph preventing refresh to be called too quickly + @param event: Mouse event + """ + if self.Is3DCanvas(): + # Call default function at most 10 times per second + current_time = gettime() + 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 + @return: Function adding text to figure + """ + text_func = (self.Axes.text2D if self.Is3DCanvas() else self.Axes.text) + def AddText(*args, **kwargs): + args = [0, 0, ""] + kwargs["transform"] = self.Axes.transAxes + return text_func(*args, **kwargs) + return AddText + + def ResetGraphics(self): + """ + Reset figure and graphical elements displayed in it + 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.Axes.set_color_cycle(['b']) + + # 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.Axes.set_color_cycle(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 + 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(): + # Items variable labels are in figure upper left corner + 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, + horizontalalignment='right')) + + # Graph type is orthogonal in 2D + else: + # X coordinate labels are in figure lower side + self.AxesLabels.append(add_text_func(size='small')) + 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')) + 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): - canvas_ratio = 1. / height - graph_ratio = 1. / ((1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) * height) - + """ + Function called when mouse leave Viewer + @param event: wx.MouseEvent + """ + # Figure position like text position in figure are expressed is ratio + # canvas size and figure size. As we want that border around figure and + # text position in figure don't change when canvas size change, we + # expressed border and text position in pixel on screen and apply the + # ratio calculated hereafter to get border and text position in + # matplotlib coordinate + canvas_ratio = 1. / height # Divide by canvas height in pixel + 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, 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 + AXES_LABEL_HEIGHT * idx) * graph_ratio)) + 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 + (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio)) else: - self.AxesLabels[0].set_position((0.1, CANVAS_PADDING * graph_ratio)) - self.Labels[0].set_position((0.95, CANVAS_PADDING * graph_ratio)) - 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)) - + # X coordinate labels are in figure lower side + self.AxesLabels[0].set_position( + (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 GetCanvasMinSize(self): - return wx.Size(200, - CANVAS_BORDER[0] + CANVAS_BORDER[1] + - 2 * CANVAS_PADDING + VALUE_LABEL_HEIGHT * len(self.Items)) - - def SetCanvasSize(self, width, height): - height = max(height, self.GetCanvasMinSize()[1]) - self.SetMinSize(wx.Size(width, height)) - self.RefreshLabelsPosition(height) - self.ParentWindow.RefreshGraphicsSizer() - - def GetAxesBoundingBox(self, absolute=False): - width, height = self.GetSize() - 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 absolute: - xw, yw = self.GetPosition() - bbox.x += xw - bbox.y += yw - return bbox - - def RefreshHighlight(self, x, y): - width, height = self.GetSize() - bbox = self.GetAxesBoundingBox() - if bbox.InsideXY(x, y) and not self.Is3DCanvas(): - rect = wx.Rect(bbox.x, bbox.y, bbox.width / 2, bbox.height) - if rect.InsideXY(x, y): - self.SetHighlight(HIGHLIGHT_LEFT) - else: - self.SetHighlight(HIGHLIGHT_RIGHT) - elif y < height / 2: - if self.ParentWindow.IsViewerFirst(self): - self.SetHighlight(HIGHLIGHT_BEFORE) - else: - self.SetHighlight(HIGHLIGHT_NONE) - self.ParentWindow.HighlightPreviousViewer(self) - else: - self.SetHighlight(HIGHLIGHT_AFTER) - - def ResetGraphics(self): - self.Figure.clear() - if self.Is3DCanvas(): - self.Axes = self.Figure.gca(projection='3d') - self.Axes.set_color_cycle(['b']) - self.LastMotionTime = gettime() - setattr(self.Axes, "_on_move", self.OnAxesMotion) - self.Axes.mouse_init() - self.Axes.tick_params(axis='z', labelsize='small') - else: - self.Axes = self.Figure.gca() - self.Axes.set_color_cycle(COLOR_CYCLE) - self.Axes.tick_params(axis='x', labelsize='small') - self.Axes.tick_params(axis='y', labelsize='small') - self.Plots = [] - self.VLine = None - self.HLine = None - self.Labels = [] - self.AxesLabels = [] - if not self.Is3DCanvas(): - text_func = self.Axes.text - else: - text_func = self.Axes.text2D - if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): - num_item = len(self.Items) - for idx in xrange(num_item): - if num_item == 1: - color = 'k' - else: - color = COLOR_CYCLE[idx % len(COLOR_CYCLE)] - if not self.Is3DCanvas(): - self.AxesLabels.append( - text_func(0, 0, "", size='small', - verticalalignment='top', - color=color, - transform=self.Axes.transAxes)) - self.Labels.append( - text_func(0, 0, "", size='large', - horizontalalignment='right', - color=color, - transform=self.Axes.transAxes)) - else: - self.AxesLabels.append( - self.Axes.text(0, 0, "", size='small', - transform=self.Axes.transAxes)) - self.Labels.append( - self.Axes.text(0, 0, "", size='large', - horizontalalignment='right', - transform=self.Axes.transAxes)) - self.AxesLabels.append( - self.Axes.text(0, 0, "", size='small', - rotation='vertical', - verticalalignment='bottom', - transform=self.Axes.transAxes)) - self.Labels.append( - self.Axes.text(0, 0, "", size='large', - rotation='vertical', - verticalalignment='top', - transform=self.Axes.transAxes)) - width, height = self.GetSize() - self.RefreshLabelsPosition(height) - - def SetCursorTick(self, cursor_tick): - self.CursorTick = cursor_tick - def RefreshViewer(self, refresh_graphics=True): if refresh_graphics: @@ -944,9 +1074,7 @@ if self.VLine is not None: self.VLine.set_visible(False) else: - min_start_tick = reduce(max, [item.GetData()[0, 0] - for item in self.Items - if len(item.GetData()) > 0], 0) + min_start_tick = max(start_tick, self.GetItemsMinCommonTick()) start_tick = max(start_tick, min_start_tick) end_tick = max(end_tick, min_start_tick) items = self.ItemsDict.values() diff -r e3f805ba74ca -r b351d3a7917c controls/DebugVariablePanel/DebugVariableViewer.py --- a/controls/DebugVariablePanel/DebugVariableViewer.py Mon Jun 03 08:41:10 2013 +0200 +++ b/controls/DebugVariablePanel/DebugVariableViewer.py Mon Jun 03 11:11:46 2013 +0200 @@ -160,6 +160,15 @@ for item in self.Items: item.ResetData() + def GetItemsMinCommonTick(self): + """ + Return the minimum tick common to all iems displayed in Viewer + @return: Minimum common tick between items + """ + return reduce(max, [item.GetData()[0, 0] + for item in self.Items + if len(item.GetData()) > 0], 0) + def RefreshViewer(self): """ Method that refresh the content displayed by Viewer