81 # Update minimal range value |
82 # Update minimal range value |
82 if min_value is None: |
83 if min_value is None: |
83 min_value = range_min |
84 min_value = range_min |
84 elif range_min is not None: |
85 elif range_min is not None: |
85 min_value = min(min_value, range_min) |
86 min_value = min(min_value, range_min) |
86 |
87 |
87 # Update maximal range value |
88 # Update maximal range value |
88 if max_value is None: |
89 if max_value is None: |
89 max_value = range_max |
90 max_value = range_max |
90 elif range_min is not None: |
91 elif range_min is not None: |
91 max_value = max(max_value, range_max) |
92 max_value = max(max_value, range_max) |
92 |
93 |
93 # Calculate range center and width if at least one valid range is defined |
94 # Calculate range center and width if at least one valid range is defined |
94 if min_value is not None and max_value is not None: |
95 if min_value is not None and max_value is not None: |
95 center = (min_value + max_value) / 2. |
96 center = (min_value + max_value) / 2. |
96 range_size = max(1.0, max_value - min_value) |
97 range_size = max(1.0, max_value - min_value) |
97 |
98 |
98 # Set default center and with if no valid range is defined |
99 # Set default center and with if no valid range is defined |
99 else: |
100 else: |
100 center = 0.5 |
101 center = 0.5 |
101 range_size = 1.0 |
102 range_size = 1.0 |
102 |
103 |
103 # Return range expended from 10 % |
104 # Return range expended from 10 % |
104 return center - range_size * 0.55, center + range_size * 0.55 |
105 return center - range_size * 0.55, center + range_size * 0.55 |
105 |
106 |
106 #------------------------------------------------------------------------------- |
107 # ------------------------------------------------------------------------------- |
107 # Debug Variable Graphic Viewer Drop Target |
108 # Debug Variable Graphic Viewer Drop Target |
108 #------------------------------------------------------------------------------- |
109 # ------------------------------------------------------------------------------- |
109 |
110 |
110 """ |
|
111 Class that implements a custom drop target class for Debug Variable Graphic |
|
112 Viewer |
|
113 """ |
|
114 |
111 |
115 class DebugVariableGraphicDropTarget(wx.TextDropTarget): |
112 class DebugVariableGraphicDropTarget(wx.TextDropTarget): |
116 |
113 """ |
|
114 Class that implements a custom drop target class for Debug Variable Graphic |
|
115 Viewer |
|
116 """ |
|
117 |
117 def __init__(self, parent, window): |
118 def __init__(self, parent, window): |
118 """ |
119 """ |
119 Constructor |
120 Constructor |
120 @param parent: Reference to Debug Variable Graphic Viewer |
121 @param parent: Reference to Debug Variable Graphic Viewer |
121 @param window: Reference to the Debug Variable Panel |
122 @param window: Reference to the Debug Variable Panel |
122 """ |
123 """ |
123 wx.TextDropTarget.__init__(self) |
124 wx.TextDropTarget.__init__(self) |
124 self.ParentControl = parent |
125 self.ParentControl = parent |
125 self.ParentWindow = window |
126 self.ParentWindow = window |
126 |
127 |
127 def __del__(self): |
128 def __del__(self): |
128 """ |
129 """ |
129 Destructor |
130 Destructor |
130 """ |
131 """ |
131 # Remove reference to Debug Variable Graphic Viewer and Debug Variable |
132 # Remove reference to Debug Variable Graphic Viewer and Debug Variable |
132 # Panel |
133 # Panel |
133 self.ParentControl = None |
134 self.ParentControl = None |
134 self.ParentWindow = None |
135 self.ParentWindow = None |
135 |
136 |
136 def OnDragOver(self, x, y, d): |
137 def OnDragOver(self, x, y, d): |
137 """ |
138 """ |
138 Function called when mouse is dragged over Drop Target |
139 Function called when mouse is dragged over Drop Target |
139 @param x: X coordinate of mouse pointer |
140 @param x: X coordinate of mouse pointer |
140 @param y: Y coordinate of mouse pointer |
141 @param y: Y coordinate of mouse pointer |
141 @param d: Suggested default for return value |
142 @param d: Suggested default for return value |
142 """ |
143 """ |
143 # Signal parent that mouse is dragged over |
144 # Signal parent that mouse is dragged over |
144 self.ParentControl.OnMouseDragging(x, y) |
145 self.ParentControl.OnMouseDragging(x, y) |
145 |
146 |
146 return wx.TextDropTarget.OnDragOver(self, x, y, d) |
147 return wx.TextDropTarget.OnDragOver(self, x, y, d) |
147 |
148 |
148 def OnDropText(self, x, y, data): |
149 def OnDropText(self, x, y, data): |
149 """ |
150 """ |
150 Function called when mouse is released in Drop Target |
151 Function called when mouse is released in Drop Target |
151 @param x: X coordinate of mouse pointer |
152 @param x: X coordinate of mouse pointer |
152 @param y: Y coordinate of mouse pointer |
153 @param y: Y coordinate of mouse pointer |
153 @param data: Text associated to drag'n drop |
154 @param data: Text associated to drag'n drop |
154 """ |
155 """ |
155 # Signal Debug Variable Panel to reset highlight |
156 # Signal Debug Variable Panel to reset highlight |
156 self.ParentWindow.ResetHighlight() |
157 self.ParentWindow.ResetHighlight() |
157 |
158 |
158 message = None |
159 message = None |
159 |
160 |
160 # Check that data is valid regarding DebugVariablePanel |
161 # Check that data is valid regarding DebugVariablePanel |
161 try: |
162 try: |
162 values = eval(data) |
163 values = eval(data) |
163 if not isinstance(values, TupleType): |
164 if not isinstance(values, TupleType): |
164 raise ValueError |
165 raise ValueError |
165 except: |
166 except Exception: |
166 message = _("Invalid value \"%s\" for debug variable")%data |
167 message = _("Invalid value \"%s\" for debug variable") % data |
167 values = None |
168 values = None |
168 |
169 |
169 # Display message if data is invalid |
170 # Display message if data is invalid |
170 if message is not None: |
171 if message is not None: |
171 wx.CallAfter(self.ShowMessage, message) |
172 wx.CallAfter(self.ShowMessage, message) |
172 |
173 |
173 # Data contain a reference to a variable to debug |
174 # Data contain a reference to a variable to debug |
174 elif values[1] == "debug": |
175 elif values[1] == "debug": |
175 target_idx = self.ParentControl.GetIndex() |
176 target_idx = self.ParentControl.GetIndex() |
176 |
177 |
177 # If mouse is dropped in graph canvas bounding box and graph is |
178 # If mouse is dropped in graph canvas bounding box and graph is |
178 # not 3D canvas, graphs will be merged |
179 # not 3D canvas, graphs will be merged |
179 rect = self.ParentControl.GetAxesBoundingBox() |
180 rect = self.ParentControl.GetAxesBoundingBox() |
180 if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y): |
181 if not self.ParentControl.Is3DCanvas() and rect.InsideXY(x, y): |
181 # Default merge type is parallel |
182 # Default merge type is parallel |
182 merge_type = GRAPH_PARALLEL |
183 merge_type = GRAPH_PARALLEL |
183 |
184 |
184 # If mouse is dropped in left part of graph canvas, graph |
185 # If mouse is dropped in left part of graph canvas, graph |
185 # wall be merged orthogonally |
186 # wall be merged orthogonally |
186 merge_rect = wx.Rect(rect.x, rect.y, |
187 merge_rect = wx.Rect(rect.x, rect.y, |
187 rect.width / 2., rect.height) |
188 rect.width / 2., rect.height) |
188 if merge_rect.InsideXY(x, y): |
189 if merge_rect.InsideXY(x, y): |
189 merge_type = GRAPH_ORTHOGONAL |
190 merge_type = GRAPH_ORTHOGONAL |
190 |
191 |
191 # Merge graphs |
192 # Merge graphs |
192 wx.CallAfter(self.ParentWindow.MergeGraphs, |
193 wx.CallAfter(self.ParentWindow.MergeGraphs, |
193 values[0], target_idx, |
194 values[0], target_idx, |
194 merge_type, force=True) |
195 merge_type, force=True) |
195 |
196 |
196 else: |
197 else: |
197 width, height = self.ParentControl.GetSize() |
198 width, height = self.ParentControl.GetSize() |
198 |
199 |
199 # Get Before which Viewer the variable has to be moved or added |
200 # Get Before which Viewer the variable has to be moved or added |
200 # according to the position of mouse in Viewer. |
201 # according to the position of mouse in Viewer. |
201 if y > height / 2: |
202 if y > height / 2: |
202 target_idx += 1 |
203 target_idx += 1 |
203 |
204 |
204 # Drag'n Drop is an internal is an internal move inside Debug |
205 # Drag'n Drop is an internal is an internal move inside Debug |
205 # Variable Panel |
206 # Variable Panel |
206 if len(values) > 2 and values[2] == "move": |
207 if len(values) > 2 and values[2] == "move": |
207 self.ParentWindow.MoveValue(values[0], |
208 self.ParentWindow.MoveValue(values[0], |
208 target_idx) |
209 target_idx) |
209 |
210 |
210 # Drag'n Drop was initiated by another control of Beremiz |
211 # Drag'n Drop was initiated by another control of Beremiz |
211 else: |
212 else: |
212 self.ParentWindow.InsertValue(values[0], |
213 self.ParentWindow.InsertValue(values[0], |
213 target_idx, |
214 target_idx, |
214 force=True) |
215 force=True) |
215 |
216 |
216 def OnLeave(self): |
217 def OnLeave(self): |
217 """ |
218 """ |
218 Function called when mouse is leave Drop Target |
219 Function called when mouse is leave Drop Target |
219 """ |
220 """ |
220 # Signal Debug Variable Panel to reset highlight |
221 # Signal Debug Variable Panel to reset highlight |
221 self.ParentWindow.ResetHighlight() |
222 self.ParentWindow.ResetHighlight() |
222 return wx.TextDropTarget.OnLeave(self) |
223 return wx.TextDropTarget.OnLeave(self) |
223 |
224 |
224 def ShowMessage(self, message): |
225 def ShowMessage(self, message): |
225 """ |
226 """ |
226 Show error message in Error Dialog |
227 Show error message in Error Dialog |
227 @param message: Error message to display |
228 @param message: Error message to display |
228 """ |
229 """ |
229 dialog = wx.MessageDialog(self.ParentWindow, |
230 dialog = wx.MessageDialog(self.ParentWindow, |
230 message, |
231 message, |
231 _("Error"), |
232 _("Error"), |
232 wx.OK|wx.ICON_ERROR) |
233 wx.OK | wx.ICON_ERROR) |
233 dialog.ShowModal() |
234 dialog.ShowModal() |
234 dialog.Destroy() |
235 dialog.Destroy() |
235 |
236 |
236 |
237 |
237 #------------------------------------------------------------------------------- |
238 # ------------------------------------------------------------------------------- |
238 # Debug Variable Graphic Viewer Class |
239 # Debug Variable Graphic Viewer Class |
239 #------------------------------------------------------------------------------- |
240 # ------------------------------------------------------------------------------- |
240 |
241 |
241 """ |
|
242 Class that implements a Viewer that display variable values as a graphs |
|
243 """ |
|
244 |
242 |
245 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas): |
243 class DebugVariableGraphicViewer(DebugVariableViewer, FigureCanvas): |
246 |
244 """ |
|
245 Class that implements a Viewer that display variable values as a graphs |
|
246 """ |
|
247 |
247 def __init__(self, parent, window, items, graph_type): |
248 def __init__(self, parent, window, items, graph_type): |
248 """ |
249 """ |
249 Constructor |
250 Constructor |
250 @param parent: Parent wx.Window of DebugVariableText |
251 @param parent: Parent wx.Window of DebugVariableText |
251 @param window: Reference to the Debug Variable Panel |
252 @param window: Reference to the Debug Variable Panel |
252 @param items: List of DebugVariableItem displayed by Viewer |
253 @param items: List of DebugVariableItem displayed by Viewer |
253 @param graph_type: Graph display type (Parallel or orthogonal) |
254 @param graph_type: Graph display type (Parallel or orthogonal) |
254 """ |
255 """ |
255 DebugVariableViewer.__init__(self, window, items) |
256 DebugVariableViewer.__init__(self, window, items) |
256 |
257 |
257 self.GraphType = graph_type # Graph type display |
258 self.GraphType = graph_type # Graph type display |
258 self.CursorTick = None # Tick of the graph cursor |
259 self.CursorTick = None # Tick of the graph cursor |
259 |
260 |
260 # Mouse position when start dragging |
261 # Mouse position when start dragging |
261 self.MouseStartPos = None |
262 self.MouseStartPos = None |
262 # Tick when moving tick start |
263 # Tick when moving tick start |
263 self.StartCursorTick = None |
264 self.StartCursorTick = None |
264 # Canvas size when starting to resize canvas |
265 # Canvas size when starting to resize canvas |
265 self.CanvasStartSize = None |
266 self.CanvasStartSize = None |
266 |
267 |
267 # List of current displayed contextual buttons |
268 # List of current displayed contextual buttons |
268 self.ContextualButtons = [] |
269 self.ContextualButtons = [] |
269 # Reference to item for which contextual buttons was displayed |
270 # Reference to item for which contextual buttons was displayed |
270 self.ContextualButtonsItem = None |
271 self.ContextualButtonsItem = None |
271 |
272 |
272 # Flag indicating that zoom fit current displayed data range or whole |
273 # Flag indicating that zoom fit current displayed data range or whole |
273 # data range if False |
274 # data range if False |
274 self.ZoomFit = False |
275 self.ZoomFit = False |
275 |
276 |
276 # Create figure for drawing graphs |
277 # Create figure for drawing graphs |
277 self.Figure = matplotlib.figure.Figure(facecolor='w') |
278 self.Figure = matplotlib.figure.Figure(facecolor='w') |
278 # Defined border around figure in canvas |
279 # Defined border around figure in canvas |
279 self.Figure.subplotpars.update(top=0.95, left=0.1, |
280 self.Figure.subplotpars.update(top=0.95, left=0.1, |
280 bottom=0.1, right=0.95) |
281 bottom=0.1, right=0.95) |
281 |
282 |
282 FigureCanvas.__init__(self, parent, -1, self.Figure) |
283 FigureCanvas.__init__(self, parent, -1, self.Figure) |
283 self.SetWindowStyle(wx.WANTS_CHARS) |
284 self.SetWindowStyle(wx.WANTS_CHARS) |
284 self.SetBackgroundColour(wx.WHITE) |
285 self.SetBackgroundColour(wx.WHITE) |
285 |
286 |
286 # Bind wx events |
287 # Bind wx events |
287 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) |
288 self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick) |
288 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) |
289 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) |
289 self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) |
290 self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter) |
290 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) |
291 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave) |
291 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) |
292 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) |
292 self.Bind(wx.EVT_SIZE, self.OnResize) |
293 self.Bind(wx.EVT_SIZE, self.OnResize) |
293 |
294 |
294 # Set canvas min size |
295 # Set canvas min size |
295 canvas_size = self.GetCanvasMinSize() |
296 canvas_size = self.GetCanvasMinSize() |
296 self.SetMinSize(canvas_size) |
297 self.SetMinSize(canvas_size) |
297 |
298 |
298 # Define Viewer drop target |
299 # Define Viewer drop target |
299 self.SetDropTarget(DebugVariableGraphicDropTarget(self, window)) |
300 self.SetDropTarget(DebugVariableGraphicDropTarget(self, window)) |
300 |
301 |
301 # Connect matplotlib events |
302 # Connect matplotlib events |
302 self.mpl_connect('button_press_event', self.OnCanvasButtonPressed) |
303 self.mpl_connect('button_press_event', self.OnCanvasButtonPressed) |
303 self.mpl_connect('motion_notify_event', self.OnCanvasMotion) |
304 self.mpl_connect('motion_notify_event', self.OnCanvasMotion) |
304 self.mpl_connect('button_release_event', self.OnCanvasButtonReleased) |
305 self.mpl_connect('button_release_event', self.OnCanvasButtonReleased) |
305 self.mpl_connect('scroll_event', self.OnCanvasScroll) |
306 self.mpl_connect('scroll_event', self.OnCanvasScroll) |
306 |
307 |
307 # Add buttons for zooming on current displayed data range |
308 # Add buttons for zooming on current displayed data range |
308 self.Buttons.append( |
309 self.Buttons.append( |
309 GraphButton(0, 0, "fit_graph", self.OnZoomFitButton)) |
310 GraphButton(0, 0, "fit_graph", self.OnZoomFitButton)) |
310 |
311 |
311 # Add buttons for changing canvas size with predefined height |
312 # Add buttons for changing canvas size with predefined height |
312 for size, bitmap in zip( |
313 for size, bitmap in zip( |
313 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI], |
314 [SIZE_MINI, SIZE_MIDDLE, SIZE_MAXI], |
314 ["minimize_graph", "middle_graph", "maximize_graph"]): |
315 ["minimize_graph", "middle_graph", "maximize_graph"]): |
315 self.Buttons.append( |
316 self.Buttons.append( |
316 GraphButton(0, 0, bitmap, |
317 GraphButton(0, 0, bitmap, |
317 self.GetOnChangeSizeButton(size))) |
318 self.GetOnChangeSizeButton(size))) |
318 |
319 |
319 # Add buttons for exporting graph values to clipboard and close graph |
320 # Add buttons for exporting graph values to clipboard and close graph |
320 for bitmap, callback in [ |
321 for bitmap, callback in [ |
321 ("export_graph_mini", self.OnExportGraphButton), |
322 ("export_graph_mini", self.OnExportGraphButton), |
322 ("delete_graph", self.OnCloseButton)]: |
323 ("delete_graph", self.OnCloseButton)]: |
323 self.Buttons.append(GraphButton(0, 0, bitmap, callback)) |
324 self.Buttons.append(GraphButton(0, 0, bitmap, callback)) |
324 |
325 |
325 # Update graphs elements |
326 # Update graphs elements |
326 self.ResetGraphics() |
327 self.ResetGraphics() |
327 self.RefreshLabelsPosition(canvas_size.height) |
328 self.RefreshLabelsPosition(canvas_size.height) |
328 |
329 |
329 def AddItem(self, item): |
330 def AddItem(self, item): |
330 """ |
331 """ |
331 Add an item to the list of items displayed by Viewer |
332 Add an item to the list of items displayed by Viewer |
332 @param item: Item to add to the list |
333 @param item: Item to add to the list |
333 """ |
334 """ |
334 DebugVariableViewer.AddItem(self, item) |
335 DebugVariableViewer.AddItem(self, item) |
335 self.ResetGraphics() |
336 self.ResetGraphics() |
336 |
337 |
337 def RemoveItem(self, item): |
338 def RemoveItem(self, item): |
338 """ |
339 """ |
339 Remove an item from the list of items displayed by Viewer |
340 Remove an item from the list of items displayed by Viewer |
340 @param item: Item to remove from the list |
341 @param item: Item to remove from the list |
341 """ |
342 """ |
342 DebugVariableViewer.RemoveItem(self, item) |
343 DebugVariableViewer.RemoveItem(self, item) |
343 |
344 |
344 # If list of items is not empty |
345 # If list of items is not empty |
345 if not self.ItemsIsEmpty(): |
346 if not self.ItemsIsEmpty(): |
346 # Return to parallel graph if there is only one item |
347 # Return to parallel graph if there is only one item |
347 # especially if it's actually orthogonal |
348 # especially if it's actually orthogonal |
348 if len(self.Items) == 1: |
349 if len(self.Items) == 1: |
349 self.GraphType = GRAPH_PARALLEL |
350 self.GraphType = GRAPH_PARALLEL |
350 self.ResetGraphics() |
351 self.ResetGraphics() |
351 |
352 |
352 def SetCursorTick(self, cursor_tick): |
353 def SetCursorTick(self, cursor_tick): |
353 """ |
354 """ |
354 Set cursor tick |
355 Set cursor tick |
355 @param cursor_tick: Cursor tick |
356 @param cursor_tick: Cursor tick |
356 """ |
357 """ |
357 self.CursorTick = cursor_tick |
358 self.CursorTick = cursor_tick |
358 |
359 |
359 def SetZoomFit(self, zoom_fit): |
360 def SetZoomFit(self, zoom_fit): |
360 """ |
361 """ |
361 Set flag indicating that zoom fit current displayed data range |
362 Set flag indicating that zoom fit current displayed data range |
362 @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range) |
363 @param zoom_fit: Flag for zoom fit (False: zoom fit whole data range) |
363 """ |
364 """ |
364 # Flag is different from the actual one |
365 # Flag is different from the actual one |
365 if zoom_fit != self.ZoomFit: |
366 if zoom_fit != self.ZoomFit: |
366 # Save new flag value |
367 # Save new flag value |
367 self.ZoomFit = zoom_fit |
368 self.ZoomFit = zoom_fit |
368 |
369 |
369 # Update button for zoom fit bitmap |
370 # Update button for zoom fit bitmap |
370 self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph") |
371 self.Buttons[0].SetBitmap("full_graph" if zoom_fit else "fit_graph") |
371 |
372 |
372 # Refresh canvas |
373 # Refresh canvas |
373 self.RefreshViewer() |
374 self.RefreshViewer() |
374 |
375 |
375 def SubscribeAllDataConsumers(self): |
376 def SubscribeAllDataConsumers(self): |
376 """ |
377 """ |
377 Function that unsubscribe and remove every item that store values of |
378 Function that unsubscribe and remove every item that store values of |
378 a variable that doesn't exist in PLC anymore |
379 a variable that doesn't exist in PLC anymore |
379 """ |
380 """ |
380 DebugVariableViewer.SubscribeAllDataConsumers(self) |
381 DebugVariableViewer.SubscribeAllDataConsumers(self) |
381 |
382 |
382 # Graph still have data to display |
383 # Graph still have data to display |
383 if not self.ItemsIsEmpty(): |
384 if not self.ItemsIsEmpty(): |
384 # Reset flag indicating that zoom fit current displayed data range |
385 # Reset flag indicating that zoom fit current displayed data range |
385 self.SetZoomFit(False) |
386 self.SetZoomFit(False) |
386 |
387 |
387 self.ResetGraphics() |
388 self.ResetGraphics() |
388 |
389 |
389 def Is3DCanvas(self): |
390 def Is3DCanvas(self): |
390 """ |
391 """ |
391 Return if Viewer is a 3D canvas |
392 Return if Viewer is a 3D canvas |
392 @return: True if Viewer is a 3D canvas |
393 @return: True if Viewer is a 3D canvas |
393 """ |
394 """ |
394 return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3 |
395 return self.GraphType == GRAPH_ORTHOGONAL and len(self.Items) == 3 |
395 |
396 |
396 def GetButtons(self): |
397 def GetButtons(self): |
397 """ |
398 """ |
398 Return list of buttons defined in Viewer |
399 Return list of buttons defined in Viewer |
399 @return: List of buttons |
400 @return: List of buttons |
400 """ |
401 """ |
401 # Add contextual buttons to default buttons |
402 # Add contextual buttons to default buttons |
402 return self.Buttons + self.ContextualButtons |
403 return self.Buttons + self.ContextualButtons |
403 |
404 |
404 def PopupContextualButtons(self, item, rect, direction=wx.RIGHT): |
405 def PopupContextualButtons(self, item, rect, direction=wx.RIGHT): |
405 """ |
406 """ |
406 Show contextual menu for item aside a label of this item defined |
407 Show contextual menu for item aside a label of this item defined |
407 by the bounding box of label in figure |
408 by the bounding box of label in figure |
408 @param item: Item for which contextual is shown |
409 @param item: Item for which contextual is shown |
410 @param direction: Direction in which buttons must be drawn |
411 @param direction: Direction in which buttons must be drawn |
411 """ |
412 """ |
412 # Return immediately if contextual menu for item is already shown |
413 # Return immediately if contextual menu for item is already shown |
413 if self.ContextualButtonsItem == item: |
414 if self.ContextualButtonsItem == item: |
414 return |
415 return |
415 |
416 |
416 # Close already shown contextual menu |
417 # Close already shown contextual menu |
417 self.DismissContextualButtons() |
418 self.DismissContextualButtons() |
418 |
419 |
419 # Save item for which contextual menu is shown |
420 # Save item for which contextual menu is shown |
420 self.ContextualButtonsItem = item |
421 self.ContextualButtonsItem = item |
421 |
422 |
422 # If item variable is forced, add button for release variable to |
423 # If item variable is forced, add button for release variable to |
423 # contextual menu |
424 # contextual menu |
424 if self.ContextualButtonsItem.IsForced(): |
425 if self.ContextualButtonsItem.IsForced(): |
425 self.ContextualButtons.append( |
426 self.ContextualButtons.append( |
426 GraphButton(0, 0, "release", self.OnReleaseItemButton)) |
427 GraphButton(0, 0, "release", self.OnReleaseItemButton)) |
427 |
428 |
428 # Add other buttons to contextual menu |
429 # Add other buttons to contextual menu |
429 for bitmap, callback in [ |
430 for bitmap, callback in [ |
430 ("force", self.OnForceItemButton), |
431 ("force", self.OnForceItemButton), |
431 ("export_graph_mini", self.OnExportItemGraphButton), |
432 ("export_graph_mini", self.OnExportItemGraphButton), |
432 ("delete_graph", self.OnRemoveItemButton)]: |
433 ("delete_graph", self.OnRemoveItemButton)]: |
433 self.ContextualButtons.append( |
434 self.ContextualButtons.append( |
434 GraphButton(0, 0, bitmap, callback)) |
435 GraphButton(0, 0, bitmap, callback)) |
435 |
436 |
436 # If buttons are shown at left side or upper side of rect, positions |
437 # If buttons are shown at left side or upper side of rect, positions |
437 # will be set in reverse order |
438 # will be set in reverse order |
438 buttons = self.ContextualButtons[:] |
439 buttons = self.ContextualButtons[:] |
439 if direction in [wx.TOP, wx.LEFT]: |
440 if direction in [wx.TOP, wx.LEFT]: |
440 buttons.reverse() |
441 buttons.reverse() |
441 |
442 |
442 # Set contextual menu buttons position aside rect depending on |
443 # Set contextual menu buttons position aside rect depending on |
443 # direction given |
444 # direction given |
444 offset = 0 |
445 offset = 0 |
445 for button in buttons: |
446 for button in buttons: |
446 w, h = button.GetSize() |
447 w, h = button.GetSize() |
447 if direction in [wx.LEFT, wx.RIGHT]: |
448 if direction in [wx.LEFT, wx.RIGHT]: |
448 x = rect.x + (- w - offset |
449 x = rect.x + (- w - offset |
449 if direction == wx.LEFT |
450 if direction == wx.LEFT |
450 else rect.width + offset) |
451 else rect.width + offset) |
451 y = rect.y + (rect.height - h) / 2 |
452 y = rect.y + (rect.height - h) / 2 |
452 offset += w |
453 offset += w |
453 else: |
454 else: |
454 x = rect.x + (rect.width - w ) / 2 |
455 x = rect.x + (rect.width - w) / 2 |
455 y = rect.y + (- h - offset |
456 y = rect.y + (- h - offset |
456 if direction == wx.TOP |
457 if direction == wx.TOP |
457 else rect.height + offset) |
458 else rect.height + offset) |
458 offset += h |
459 offset += h |
459 button.SetPosition(x, y) |
460 button.SetPosition(x, y) |
460 button.Show() |
461 button.Show() |
461 |
462 |
462 # Refresh canvas |
463 # Refresh canvas |
463 self.ParentWindow.ForceRefresh() |
464 self.ParentWindow.ForceRefresh() |
464 |
465 |
465 def DismissContextualButtons(self): |
466 def DismissContextualButtons(self): |
466 """ |
467 """ |
467 Close current shown contextual menu |
468 Close current shown contextual menu |
468 """ |
469 """ |
469 # Return immediately if no contextual menu is shown |
470 # Return immediately if no contextual menu is shown |
470 if self.ContextualButtonsItem is None: |
471 if self.ContextualButtonsItem is None: |
471 return |
472 return |
472 |
473 |
473 # Reset variables corresponding to contextual menu |
474 # Reset variables corresponding to contextual menu |
474 self.ContextualButtonsItem = None |
475 self.ContextualButtonsItem = None |
475 self.ContextualButtons = [] |
476 self.ContextualButtons = [] |
476 |
477 |
477 # Refresh canvas |
478 # Refresh canvas |
478 self.ParentWindow.ForceRefresh() |
479 self.ParentWindow.ForceRefresh() |
479 |
480 |
480 def IsOverContextualButton(self, x, y): |
481 def IsOverContextualButton(self, x, y): |
481 """ |
482 """ |
482 Return if point is over one contextual button of Viewer |
483 Return if point is over one contextual button of Viewer |
483 @param x: X coordinate of point |
484 @param x: X coordinate of point |
484 @param y: Y coordinate of point |
485 @param y: Y coordinate of point |
486 """ |
487 """ |
487 for button in self.ContextualButtons: |
488 for button in self.ContextualButtons: |
488 if button.HitTest(x, y): |
489 if button.HitTest(x, y): |
489 return button |
490 return button |
490 return None |
491 return None |
491 |
492 |
492 def ExportGraph(self, item=None): |
493 def ExportGraph(self, item=None): |
493 """ |
494 """ |
494 Export item(s) data to clipboard in CSV format |
495 Export item(s) data to clipboard in CSV format |
495 @param item: Item from which data to export, all items if None |
496 @param item: Item from which data to export, all items if None |
496 (default None) |
497 (default None) |
497 """ |
498 """ |
498 self.ParentWindow.CopyDataToClipboard( |
499 self.ParentWindow.CopyDataToClipboard( |
499 [(item, [entry for entry in item.GetData()]) |
500 [(item, [entry for entry in item.GetData()]) |
500 for item in (self.Items |
501 for item in (self.Items |
501 if item is None |
502 if item is None |
502 else [item])]) |
503 else [item])]) |
503 |
504 |
504 def OnZoomFitButton(self): |
505 def OnZoomFitButton(self): |
505 """ |
506 """ |
506 Function called when Viewer Zoom Fit button is pressed |
507 Function called when Viewer Zoom Fit button is pressed |
507 """ |
508 """ |
508 # Toggle zoom fit flag value |
509 # Toggle zoom fit flag value |
509 self.SetZoomFit(not self.ZoomFit) |
510 self.SetZoomFit(not self.ZoomFit) |
510 |
511 |
511 def GetOnChangeSizeButton(self, height): |
512 def GetOnChangeSizeButton(self, height): |
512 """ |
513 """ |
513 Function that generate callback function for change Viewer height to |
514 Function that generate callback function for change Viewer height to |
514 pre-defined height button |
515 pre-defined height button |
515 @param height: Height that change Viewer to |
516 @param height: Height that change Viewer to |
516 @return: callback function |
517 @return: callback function |
517 """ |
518 """ |
518 def OnChangeSizeButton(): |
519 def OnChangeSizeButton(): |
519 self.SetCanvasHeight(height) |
520 self.SetCanvasHeight(height) |
520 return OnChangeSizeButton |
521 return OnChangeSizeButton |
521 |
522 |
522 def OnExportGraphButton(self): |
523 def OnExportGraphButton(self): |
523 """ |
524 """ |
524 Function called when Viewer Export button is pressed |
525 Function called when Viewer Export button is pressed |
525 """ |
526 """ |
526 # Export data of every item in Viewer |
527 # Export data of every item in Viewer |
527 self.ExportGraph() |
528 self.ExportGraph() |
528 |
529 |
529 def OnForceItemButton(self): |
530 def OnForceItemButton(self): |
530 """ |
531 """ |
531 Function called when contextual menu Force button is pressed |
532 Function called when contextual menu Force button is pressed |
532 """ |
533 """ |
533 # Open dialog for forcing item variable value |
534 # Open dialog for forcing item variable value |
534 self.ForceValue(self.ContextualButtonsItem) |
535 self.ForceValue(self.ContextualButtonsItem) |
535 # Close contextual menu |
536 # Close contextual menu |
536 self.DismissContextualButtons() |
537 self.DismissContextualButtons() |
537 |
538 |
538 def OnReleaseItemButton(self): |
539 def OnReleaseItemButton(self): |
539 """ |
540 """ |
540 Function called when contextual menu Release button is pressed |
541 Function called when contextual menu Release button is pressed |
541 """ |
542 """ |
542 # Release item variable value |
543 # Release item variable value |
543 self.ReleaseValue(self.ContextualButtonsItem) |
544 self.ReleaseValue(self.ContextualButtonsItem) |
544 # Close contextual menu |
545 # Close contextual menu |
545 self.DismissContextualButtons() |
546 self.DismissContextualButtons() |
546 |
547 |
547 def OnExportItemGraphButton(self): |
548 def OnExportItemGraphButton(self): |
548 """ |
549 """ |
549 Function called when contextual menu Export button is pressed |
550 Function called when contextual menu Export button is pressed |
550 """ |
551 """ |
551 # Export data of item variable |
552 # Export data of item variable |
552 self.ExportGraph(self.ContextualButtonsItem) |
553 self.ExportGraph(self.ContextualButtonsItem) |
553 # Close contextual menu |
554 # Close contextual menu |
554 self.DismissContextualButtons() |
555 self.DismissContextualButtons() |
555 |
556 |
556 def OnRemoveItemButton(self): |
557 def OnRemoveItemButton(self): |
557 """ |
558 """ |
558 Function called when contextual menu Remove button is pressed |
559 Function called when contextual menu Remove button is pressed |
559 """ |
560 """ |
560 # Remove item from Viewer |
561 # Remove item from Viewer |
561 wx.CallAfter(self.ParentWindow.DeleteValue, self, |
562 wx.CallAfter(self.ParentWindow.DeleteValue, self, |
562 self.ContextualButtonsItem) |
563 self.ContextualButtonsItem) |
563 # Close contextual menu |
564 # Close contextual menu |
564 self.DismissContextualButtons() |
565 self.DismissContextualButtons() |
565 |
566 |
566 def HandleCursorMove(self, event): |
567 def HandleCursorMove(self, event): |
567 """ |
568 """ |
568 Update Cursor position according to mouse position and graph type |
569 Update Cursor position according to mouse position and graph type |
569 @param event: Mouse event |
570 @param event: Mouse event |
570 """ |
571 """ |
571 start_tick, end_tick = self.ParentWindow.GetRange() |
572 start_tick, end_tick = self.ParentWindow.GetRange() |
572 cursor_tick = None |
573 cursor_tick = None |
573 items = self.ItemsDict.values() |
574 items = self.ItemsDict.values() |
574 |
575 |
575 # Graph is orthogonal |
576 # Graph is orthogonal |
576 if self.GraphType == GRAPH_ORTHOGONAL: |
577 if self.GraphType == GRAPH_ORTHOGONAL: |
577 # Extract items data displayed in canvas figure |
578 # Extract items data displayed in canvas figure |
578 start_tick = max(start_tick, self.GetItemsMinCommonTick()) |
579 start_tick = max(start_tick, self.GetItemsMinCommonTick()) |
579 end_tick = max(end_tick, start_tick) |
580 end_tick = max(end_tick, start_tick) |
580 x_data = items[0].GetData(start_tick, end_tick) |
581 x_data = items[0].GetData(start_tick, end_tick) |
581 y_data = items[1].GetData(start_tick, end_tick) |
582 y_data = items[1].GetData(start_tick, end_tick) |
582 |
583 |
583 # Search for the nearest point from mouse position |
584 # Search for the nearest point from mouse position |
584 if len(x_data) > 0 and len(y_data) > 0: |
585 if len(x_data) > 0 and len(y_data) > 0: |
585 length = min(len(x_data), len(y_data)) |
586 length = min(len(x_data), len(y_data)) |
586 d = numpy.sqrt((x_data[:length,1]-event.xdata) ** 2 + \ |
587 d = numpy.sqrt((x_data[:length, 1]-event.xdata) ** 2 + |
587 (y_data[:length,1]-event.ydata) ** 2) |
588 (y_data[:length, 1]-event.ydata) ** 2) |
588 |
589 |
589 # Set cursor tick to the tick of this point |
590 # Set cursor tick to the tick of this point |
590 cursor_tick = x_data[numpy.argmin(d), 0] |
591 cursor_tick = x_data[numpy.argmin(d), 0] |
591 |
592 |
592 # Graph is parallel |
593 # Graph is parallel |
593 else: |
594 else: |
594 # Extract items tick |
595 # Extract items tick |
595 data = items[0].GetData(start_tick, end_tick) |
596 data = items[0].GetData(start_tick, end_tick) |
596 |
597 |
597 # Search for point that tick is the nearest from mouse X position |
598 # Search for point that tick is the nearest from mouse X position |
598 # and set cursor tick to the tick of this point |
599 # and set cursor tick to the tick of this point |
599 if len(data) > 0: |
600 if len(data) > 0: |
600 cursor_tick = data[numpy.argmin( |
601 cursor_tick = data[numpy.argmin( |
601 numpy.abs(data[:,0] - event.xdata)), 0] |
602 numpy.abs(data[:, 0] - event.xdata)), 0] |
602 |
603 |
603 # Update cursor tick |
604 # Update cursor tick |
604 if cursor_tick is not None: |
605 if cursor_tick is not None: |
605 self.ParentWindow.SetCursorTick(cursor_tick) |
606 self.ParentWindow.SetCursorTick(cursor_tick) |
606 |
607 |
607 def OnCanvasButtonPressed(self, event): |
608 def OnCanvasButtonPressed(self, event): |
608 """ |
609 """ |
609 Function called when a button of mouse is pressed |
610 Function called when a button of mouse is pressed |
610 @param event: Mouse event |
611 @param event: Mouse event |
611 """ |
612 """ |
612 # Get mouse position, graph Y coordinate is inverted in matplotlib |
613 # Get mouse position, graph Y coordinate is inverted in matplotlib |
613 # comparing to wx |
614 # comparing to wx |
614 width, height = self.GetSize() |
615 width, height = self.GetSize() |
615 x, y = event.x, height - event.y |
616 x, y = event.x, height - event.y |
616 |
617 |
617 # Return immediately if mouse is over a button |
618 # Return immediately if mouse is over a button |
618 if self.IsOverButton(x, y): |
619 if self.IsOverButton(x, y): |
619 return |
620 return |
620 |
621 |
621 # Mouse was clicked inside graph figure |
622 # Mouse was clicked inside graph figure |
622 if event.inaxes == self.Axes: |
623 if event.inaxes == self.Axes: |
623 |
624 |
624 # Find if it was on an item label |
625 # Find if it was on an item label |
625 item_idx = None |
626 item_idx = None |
626 # Check every label paired with corresponding item |
627 # Check every label paired with corresponding item |
627 for i, t in ([pair for pair in enumerate(self.AxesLabels)] + |
628 for i, t in ([pair for pair in enumerate(self.AxesLabels)] + |
628 [pair for pair in enumerate(self.Labels)]): |
629 [pair for pair in enumerate(self.Labels)]): |
629 # Get label bounding box |
630 # Get label bounding box |
630 (x0, y0), (x1, y1) = t.get_window_extent().get_points() |
631 (x0, y0), (x1, y1) = t.get_window_extent().get_points() |
631 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) |
632 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) |
632 # Check if mouse was over label |
633 # Check if mouse was over label |
633 if rect.InsideXY(x, y): |
634 if rect.InsideXY(x, y): |
634 item_idx = i |
635 item_idx = i |
635 break |
636 break |
636 |
637 |
637 # If an item label have been clicked |
638 # If an item label have been clicked |
638 if item_idx is not None: |
639 if item_idx is not None: |
639 # Hide buttons and contextual buttons |
640 # Hide buttons and contextual buttons |
640 self.ShowButtons(False) |
641 self.ShowButtons(False) |
641 self.DismissContextualButtons() |
642 self.DismissContextualButtons() |
642 |
643 |
643 # Start a drag'n drop from mouse position in wx coordinate of |
644 # Start a drag'n drop from mouse position in wx coordinate of |
644 # parent |
645 # parent |
645 xw, yw = self.GetPosition() |
646 xw, yw = self.GetPosition() |
646 self.ParentWindow.StartDragNDrop(self, |
647 self.ParentWindow.StartDragNDrop( |
647 self.ItemsDict.values()[item_idx], |
648 self, self.ItemsDict.values()[item_idx], |
648 x + xw, y + yw, # Current mouse position |
649 x + xw, y + yw, # Current mouse position |
649 x + xw, y + yw) # Mouse position when button was clicked |
650 x + xw, y + yw) # Mouse position when button was clicked |
650 |
651 |
651 # Don't handle mouse button if canvas is 3D and let matplotlib do |
652 # Don't handle mouse button if canvas is 3D and let matplotlib do |
652 # the default behavior (rotate 3D axes) |
653 # the default behavior (rotate 3D axes) |
653 elif not self.Is3DCanvas(): |
654 elif not self.Is3DCanvas(): |
654 # Save mouse position when clicked |
655 # Save mouse position when clicked |
655 self.MouseStartPos = wx.Point(x, y) |
656 self.MouseStartPos = wx.Point(x, y) |
656 |
657 |
657 # Mouse button was left button, start moving cursor |
658 # Mouse button was left button, start moving cursor |
658 if event.button == 1: |
659 if event.button == 1: |
659 # Save current tick in case a drag'n drop is initiate to |
660 # Save current tick in case a drag'n drop is initiate to |
660 # restore it |
661 # restore it |
661 self.StartCursorTick = self.CursorTick |
662 self.StartCursorTick = self.CursorTick |
662 |
663 |
663 self.HandleCursorMove(event) |
664 self.HandleCursorMove(event) |
664 |
665 |
665 # Mouse button is middle button and graph is parallel, start |
666 # Mouse button is middle button and graph is parallel, start |
666 # moving graph along X coordinate (tick) |
667 # moving graph along X coordinate (tick) |
667 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL: |
668 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL: |
668 self.StartCursorTick = self.ParentWindow.GetRange()[0] |
669 self.StartCursorTick = self.ParentWindow.GetRange()[0] |
669 |
670 |
670 # Mouse was clicked outside graph figure and over resize highlight with |
671 # Mouse was clicked outside graph figure and over resize highlight with |
671 # left button, start resizing Viewer |
672 # left button, start resizing Viewer |
672 elif event.button == 1 and event.y <= 5: |
673 elif event.button == 1 and event.y <= 5: |
673 self.MouseStartPos = wx.Point(x, y) |
674 self.MouseStartPos = wx.Point(x, y) |
674 self.CanvasStartSize = height |
675 self.CanvasStartSize = height |
675 |
676 |
676 def OnCanvasButtonReleased(self, event): |
677 def OnCanvasButtonReleased(self, event): |
677 """ |
678 """ |
678 Function called when a button of mouse is released |
679 Function called when a button of mouse is released |
679 @param event: Mouse event |
680 @param event: Mouse event |
680 """ |
681 """ |
683 width, height = self.GetSize() |
684 width, height = self.GetSize() |
684 xw, yw = self.GetPosition() |
685 xw, yw = self.GetPosition() |
685 item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0] |
686 item = self.ParentWindow.DraggingAxesPanel.ItemsDict.values()[0] |
686 # Give mouse position in wx coordinate of parent |
687 # Give mouse position in wx coordinate of parent |
687 self.ParentWindow.StopDragNDrop(item.GetVariable(), |
688 self.ParentWindow.StopDragNDrop(item.GetVariable(), |
688 xw + event.x, yw + height - event.y) |
689 xw + event.x, yw + height - event.y) |
689 |
690 |
690 else: |
691 else: |
691 # Reset any move in progress |
692 # Reset any move in progress |
692 self.MouseStartPos = None |
693 self.MouseStartPos = None |
693 self.CanvasStartSize = None |
694 self.CanvasStartSize = None |
694 |
695 |
695 # Handle button under mouse if it exist |
696 # Handle button under mouse if it exist |
696 width, height = self.GetSize() |
697 width, height = self.GetSize() |
697 self.HandleButton(event.x, height - event.y) |
698 self.HandleButton(event.x, height - event.y) |
698 |
699 |
699 def OnCanvasMotion(self, event): |
700 def OnCanvasMotion(self, event): |
700 """ |
701 """ |
701 Function called when a button of mouse is moved over Viewer |
702 Function called when a button of mouse is moved over Viewer |
702 @param event: Mouse event |
703 @param event: Mouse event |
703 """ |
704 """ |
704 width, height = self.GetSize() |
705 width, height = self.GetSize() |
705 |
706 |
706 # If a drag'n drop is in progress, move canvas dragged |
707 # If a drag'n drop is in progress, move canvas dragged |
707 if self.ParentWindow.IsDragging(): |
708 if self.ParentWindow.IsDragging(): |
708 xw, yw = self.GetPosition() |
709 xw, yw = self.GetPosition() |
709 # Give mouse position in wx coordinate of parent |
710 # Give mouse position in wx coordinate of parent |
710 self.ParentWindow.MoveDragNDrop( |
711 self.ParentWindow.MoveDragNDrop( |
711 xw + event.x, |
712 xw + event.x, |
712 yw + height - event.y) |
713 yw + height - event.y) |
713 |
714 |
714 # If a Viewer resize is in progress, change Viewer size |
715 # If a Viewer resize is in progress, change Viewer size |
715 elif event.button == 1 and self.CanvasStartSize is not None: |
716 elif event.button == 1 and self.CanvasStartSize is not None: |
716 width, height = self.GetSize() |
717 width, height = self.GetSize() |
717 self.SetCanvasHeight( |
718 self.SetCanvasHeight( |
718 self.CanvasStartSize + height - event.y - self.MouseStartPos.y) |
719 self.CanvasStartSize + height - event.y - self.MouseStartPos.y) |
719 |
720 |
720 # If no button is pressed, show or hide contextual buttons or resize |
721 # If no button is pressed, show or hide contextual buttons or resize |
721 # highlight |
722 # highlight |
722 elif event.button is None: |
723 elif event.button is None: |
723 # Compute direction for items label according graph type |
724 # Compute direction for items label according graph type |
724 if self.GraphType == GRAPH_PARALLEL: # Graph is parallel |
725 if self.GraphType == GRAPH_PARALLEL: # Graph is parallel |
725 directions = [wx.RIGHT] * len(self.AxesLabels) + \ |
726 directions = [wx.RIGHT] * len(self.AxesLabels) + \ |
726 [wx.LEFT] * len(self.Labels) |
727 [wx.LEFT] * len(self.Labels) |
727 elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D |
728 elif len(self.AxesLabels) > 0: # Graph is orthogonal in 2D |
728 directions = [wx.RIGHT, wx.TOP, # Directions for AxesLabels |
729 directions = [wx.RIGHT, wx.TOP, # Directions for AxesLabels |
729 wx.LEFT, wx.BOTTOM] # Directions for Labels |
730 wx.LEFT, wx.BOTTOM] # Directions for Labels |
730 else: # Graph is orthogonal in 3D |
731 else: # Graph is orthogonal in 3D |
731 directions = [wx.LEFT] * len(self.Labels) |
732 directions = [wx.LEFT] * len(self.Labels) |
732 |
733 |
733 # Find if mouse is over an item label |
734 # Find if mouse is over an item label |
734 item_idx = None |
735 item_idx = None |
735 menu_direction = None |
736 menu_direction = None |
736 for (i, t), dir in zip( |
737 for (i, t), dir in zip( |
737 [pair for pair in enumerate(self.AxesLabels)] + |
738 [pair for pair in enumerate(self.AxesLabels)] + |
738 [pair for pair in enumerate(self.Labels)], |
739 [pair for pair in enumerate(self.Labels)], |
739 directions): |
740 directions): |
740 # Check every label paired with corresponding item |
741 # Check every label paired with corresponding item |
741 (x0, y0), (x1, y1) = t.get_window_extent().get_points() |
742 (x0, y0), (x1, y1) = t.get_window_extent().get_points() |
742 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) |
743 rect = wx.Rect(x0, height - y1, x1 - x0, y1 - y0) |
743 # Check if mouse was over label |
744 # Check if mouse was over label |
744 if rect.InsideXY(event.x, height - event.y): |
745 if rect.InsideXY(event.x, height - event.y): |
745 item_idx = i |
746 item_idx = i |
746 menu_direction = dir |
747 menu_direction = dir |
747 break |
748 break |
748 |
749 |
749 # If mouse is over an item label, |
750 # If mouse is over an item label, |
750 if item_idx is not None: |
751 if item_idx is not None: |
751 self.PopupContextualButtons( |
752 self.PopupContextualButtons( |
752 self.ItemsDict.values()[item_idx], |
753 self.ItemsDict.values()[item_idx], |
753 rect, menu_direction) |
754 rect, menu_direction) |
754 return |
755 return |
755 |
756 |
756 # If mouse isn't over a contextual menu, hide the current shown one |
757 # If mouse isn't over a contextual menu, hide the current shown one |
757 # if it exists |
758 # if it exists |
758 if self.IsOverContextualButton(event.x, height - event.y) is None: |
759 if self.IsOverContextualButton(event.x, height - event.y) is None: |
759 self.DismissContextualButtons() |
760 self.DismissContextualButtons() |
760 |
761 |
761 # Update resize highlight |
762 # Update resize highlight |
762 if event.y <= 5: |
763 if event.y <= 5: |
763 if self.SetHighlight(HIGHLIGHT_RESIZE): |
764 if self.SetHighlight(HIGHLIGHT_RESIZE): |
764 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS)) |
765 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZENS)) |
765 self.ParentWindow.ForceRefresh() |
766 self.ParentWindow.ForceRefresh() |
766 else: |
767 else: |
767 if self.SetHighlight(HIGHLIGHT_NONE): |
768 if self.SetHighlight(HIGHLIGHT_NONE): |
768 self.SetCursor(wx.NullCursor) |
769 self.SetCursor(wx.NullCursor) |
769 self.ParentWindow.ForceRefresh() |
770 self.ParentWindow.ForceRefresh() |
770 |
771 |
771 # Handle buttons if canvas is not 3D |
772 # Handle buttons if canvas is not 3D |
772 elif not self.Is3DCanvas(): |
773 elif not self.Is3DCanvas(): |
773 |
774 |
774 # If left button is pressed |
775 # If left button is pressed |
775 if event.button == 1: |
776 if event.button == 1: |
776 |
777 |
777 # Mouse is inside graph figure |
778 # Mouse is inside graph figure |
778 if event.inaxes == self.Axes: |
779 if event.inaxes == self.Axes: |
779 |
780 |
780 # If a cursor move is in progress, update cursor position |
781 # If a cursor move is in progress, update cursor position |
781 if self.MouseStartPos is not None: |
782 if self.MouseStartPos is not None: |
782 self.HandleCursorMove(event) |
783 self.HandleCursorMove(event) |
783 |
784 |
784 # Mouse is outside graph figure, cursor move is in progress and |
785 # Mouse is outside graph figure, cursor move is in progress and |
785 # there is only one item in Viewer, start a drag'n drop |
786 # there is only one item in Viewer, start a drag'n drop |
786 elif self.MouseStartPos is not None and len(self.Items) == 1: |
787 elif self.MouseStartPos is not None and len(self.Items) == 1: |
787 xw, yw = self.GetPosition() |
788 xw, yw = self.GetPosition() |
788 self.ParentWindow.SetCursorTick(self.StartCursorTick) |
789 self.ParentWindow.SetCursorTick(self.StartCursorTick) |
789 self.ParentWindow.StartDragNDrop(self, |
790 self.ParentWindow.StartDragNDrop( |
790 self.ItemsDict.values()[0], |
791 self, self.ItemsDict.values()[0], |
791 # Current mouse position |
792 # Current mouse position |
792 event.x + xw, height - event.y + yw, |
793 event.x + xw, height - event.y + yw, |
793 # Mouse position when button was clicked |
794 # Mouse position when button was clicked |
794 self.MouseStartPos.x + xw, |
795 self.MouseStartPos.x + xw, |
795 self.MouseStartPos.y + yw) |
796 self.MouseStartPos.y + yw) |
796 |
797 |
797 # If middle button is pressed and moving graph along X coordinate |
798 # If middle button is pressed and moving graph along X coordinate |
798 # is in progress |
799 # is in progress |
799 elif event.button == 2 and self.GraphType == GRAPH_PARALLEL and \ |
800 elif (event.button == 2 and |
800 self.MouseStartPos is not None: |
801 self.GraphType == GRAPH_PARALLEL and |
|
802 self.MouseStartPos is not None): |
801 start_tick, end_tick = self.ParentWindow.GetRange() |
803 start_tick, end_tick = self.ParentWindow.GetRange() |
802 rect = self.GetAxesBoundingBox() |
804 rect = self.GetAxesBoundingBox() |
803 |
805 |
804 # Move graph along X coordinate |
806 # Move graph along X coordinate |
805 self.ParentWindow.SetCanvasPosition( |
807 self.ParentWindow.SetCanvasPosition( |
806 self.StartCursorTick + |
808 self.StartCursorTick + |
807 (self.MouseStartPos.x - event.x) * |
809 (self.MouseStartPos.x - event.x) * |
808 (end_tick - start_tick) / rect.width) |
810 (end_tick - start_tick) / rect.width) |
809 |
811 |
810 def OnCanvasScroll(self, event): |
812 def OnCanvasScroll(self, event): |
811 """ |
813 """ |
812 Function called when a wheel mouse is use in Viewer |
814 Function called when a wheel mouse is use in Viewer |
813 @param event: Mouse event |
815 @param event: Mouse event |
814 """ |
816 """ |
815 # Change X range of graphs if mouse is in canvas figure and ctrl is |
817 # Change X range of graphs if mouse is in canvas figure and ctrl is |
816 # pressed |
818 # pressed |
817 if event.inaxes is not None and event.guiEvent.ControlDown(): |
819 if event.inaxes is not None and event.guiEvent.ControlDown(): |
818 |
820 |
819 # Calculate position of fixed tick point according to graph type |
821 # Calculate position of fixed tick point according to graph type |
820 # and mouse position |
822 # and mouse position |
821 if self.GraphType == GRAPH_ORTHOGONAL: |
823 if self.GraphType == GRAPH_ORTHOGONAL: |
822 start_tick, end_tick = self.ParentWindow.GetRange() |
824 start_tick, end_tick = self.ParentWindow.GetRange() |
823 tick = (start_tick + end_tick) / 2. |
825 tick = (start_tick + end_tick) / 2. |
824 else: |
826 else: |
825 tick = event.xdata |
827 tick = event.xdata |
826 self.ParentWindow.ChangeRange(int(-event.step) / 3, tick) |
828 self.ParentWindow.ChangeRange(int(-event.step) / 3, tick) |
827 |
829 |
828 # Vetoing event to prevent parent panel to be scrolled |
830 # Vetoing event to prevent parent panel to be scrolled |
829 self.ParentWindow.VetoScrollEvent = True |
831 self.ParentWindow.VetoScrollEvent = True |
830 |
832 |
831 def OnLeftDClick(self, event): |
833 def OnLeftDClick(self, event): |
832 """ |
834 """ |
833 Function called when a left mouse button is double clicked |
835 Function called when a left mouse button is double clicked |
834 @param event: Mouse event |
836 @param event: Mouse event |
835 """ |
837 """ |
967 # Call default function at most 10 times per second |
969 # Call default function at most 10 times per second |
968 current_time = gettime() |
970 current_time = gettime() |
969 if current_time - self.LastMotionTime > REFRESH_PERIOD: |
971 if current_time - self.LastMotionTime > REFRESH_PERIOD: |
970 self.LastMotionTime = current_time |
972 self.LastMotionTime = current_time |
971 Axes3D._on_move(self.Axes, event) |
973 Axes3D._on_move(self.Axes, event) |
972 |
974 |
973 def GetAddTextFunction(self): |
975 def GetAddTextFunction(self): |
974 """ |
976 """ |
975 Return function for adding text in figure according to graph type |
977 Return function for adding text in figure according to graph type |
976 @return: Function adding text to figure |
978 @return: Function adding text to figure |
977 """ |
979 """ |
978 text_func = (self.Axes.text2D if self.Is3DCanvas() else self.Axes.text) |
980 text_func = (self.Axes.text2D if self.Is3DCanvas() else self.Axes.text) |
|
981 |
979 def AddText(*args, **kwargs): |
982 def AddText(*args, **kwargs): |
980 args = [0, 0, ""] |
983 args = [0, 0, ""] |
981 kwargs["transform"] = self.Axes.transAxes |
984 kwargs["transform"] = self.Axes.transAxes |
982 return text_func(*args, **kwargs) |
985 return text_func(*args, **kwargs) |
983 return AddText |
986 return AddText |
984 |
987 |
985 def SetAxesColor(self, color): |
988 def SetAxesColor(self, color): |
986 if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"): |
989 if LooseVersion(matplotlib.__version__) >= LooseVersion("1.5.0"): |
987 self.Axes.set_prop_cycle(cycler('color',color)) |
990 self.Axes.set_prop_cycle(cycler('color', color)) |
988 else: |
991 else: |
989 self.Axes.set_color_cycle(color) |
992 self.Axes.set_color_cycle(color) |
990 |
993 |
991 def ResetGraphics(self): |
994 def ResetGraphics(self): |
992 """ |
995 """ |
993 Reset figure and graphical elements displayed in it |
996 Reset figure and graphical elements displayed in it |
994 Called any time list of items or graph type change |
997 Called any time list of items or graph type change |
995 """ |
998 """ |
996 # Clear figure from any axes defined |
999 # Clear figure from any axes defined |
997 self.Figure.clear() |
1000 self.Figure.clear() |
998 |
1001 |
999 # Add 3D projection if graph is in 3D |
1002 # Add 3D projection if graph is in 3D |
1000 if self.Is3DCanvas(): |
1003 if self.Is3DCanvas(): |
1001 self.Axes = self.Figure.gca(projection='3d') |
1004 self.Axes = self.Figure.gca(projection='3d') |
1002 self.SetAxesColor(['b']) |
1005 self.SetAxesColor(['b']) |
1003 |
1006 |
1004 # Override function to prevent too much refresh when graph is |
1007 # Override function to prevent too much refresh when graph is |
1005 # rotated |
1008 # rotated |
1006 self.LastMotionTime = gettime() |
1009 self.LastMotionTime = gettime() |
1007 setattr(self.Axes, "_on_move", self.OnAxesMotion) |
1010 setattr(self.Axes, "_on_move", self.OnAxesMotion) |
1008 |
1011 |
1009 # Init graph mouse event so that graph can be rotated |
1012 # Init graph mouse event so that graph can be rotated |
1010 self.Axes.mouse_init() |
1013 self.Axes.mouse_init() |
1011 |
1014 |
1012 # Set size of Z axis labels |
1015 # Set size of Z axis labels |
1013 self.Axes.tick_params(axis='z', labelsize='small') |
1016 self.Axes.tick_params(axis='z', labelsize='small') |
1014 |
1017 |
1015 else: |
1018 else: |
1016 self.Axes = self.Figure.gca() |
1019 self.Axes = self.Figure.gca() |
1017 self.SetAxesColor(COLOR_CYCLE) |
1020 self.SetAxesColor(COLOR_CYCLE) |
1018 |
1021 |
1019 # Set size of X and Y axis labels |
1022 # Set size of X and Y axis labels |
1020 self.Axes.tick_params(axis='x', labelsize='small') |
1023 self.Axes.tick_params(axis='x', labelsize='small') |
1021 self.Axes.tick_params(axis='y', labelsize='small') |
1024 self.Axes.tick_params(axis='y', labelsize='small') |
1022 |
1025 |
1023 # Init variables storing graphical elements added to figure |
1026 # Init variables storing graphical elements added to figure |
1024 self.Plots = [] # List of curves |
1027 self.Plots = [] # List of curves |
1025 self.VLine = None # Vertical line for cursor |
1028 self.VLine = None # Vertical line for cursor |
1026 self.HLine = None # Horizontal line for cursor (only orthogonal 2D) |
1029 self.HLine = None # Horizontal line for cursor (only orthogonal 2D) |
1027 self.AxesLabels = [] # List of items variable path text label |
1030 self.AxesLabels = [] # List of items variable path text label |
1028 self.Labels = [] # List of items text label |
1031 self.Labels = [] # List of items text label |
1029 |
1032 |
1030 # Get function to add a text in figure according to graph type |
1033 # Get function to add a text in figure according to graph type |
1031 add_text_func = self.GetAddTextFunction() |
1034 add_text_func = self.GetAddTextFunction() |
1032 |
1035 |
1033 # Graph type is parallel or orthogonal in 3D |
1036 # Graph type is parallel or orthogonal in 3D |
1034 if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): |
1037 if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): |
1035 num_item = len(self.Items) |
1038 num_item = len(self.Items) |
1036 for idx in xrange(num_item): |
1039 for idx in xrange(num_item): |
1037 |
1040 |
1038 # Get color from color cycle (black if only one item) |
1041 # Get color from color cycle (black if only one item) |
1039 color = ('k' if num_item == 1 |
1042 color = ('k' if num_item == 1 else |
1040 else COLOR_CYCLE[idx % len(COLOR_CYCLE)]) |
1043 COLOR_CYCLE[idx % len(COLOR_CYCLE)]) |
1041 |
1044 |
1042 # In 3D graph items variable label are not displayed as text |
1045 # In 3D graph items variable label are not displayed as text |
1043 # in figure, but as axis title |
1046 # in figure, but as axis title |
1044 if not self.Is3DCanvas(): |
1047 if not self.Is3DCanvas(): |
1045 # Items variable labels are in figure upper left corner |
1048 # Items variable labels are in figure upper left corner |
1046 self.AxesLabels.append( |
1049 self.AxesLabels.append( |
1047 add_text_func(size='small', color=color, |
1050 add_text_func(size='small', color=color, |
1048 verticalalignment='top')) |
1051 verticalalignment='top')) |
1049 |
1052 |
1050 # Items variable labels are in figure lower right corner |
1053 # Items variable labels are in figure lower right corner |
1051 self.Labels.append( |
1054 self.Labels.append( |
1052 add_text_func(size='large', color=color, |
1055 add_text_func(size='large', color=color, |
1053 horizontalalignment='right')) |
1056 horizontalalignment='right')) |
1054 |
1057 |
1055 # Graph type is orthogonal in 2D |
1058 # Graph type is orthogonal in 2D |
1056 else: |
1059 else: |
1057 # X coordinate labels are in figure lower side |
1060 # X coordinate labels are in figure lower side |
1058 self.AxesLabels.append(add_text_func(size='small')) |
1061 self.AxesLabels.append(add_text_func(size='small')) |
1059 self.Labels.append( |
1062 self.Labels.append( |
1060 add_text_func(size='large', |
1063 add_text_func(size='large', |
1061 horizontalalignment='right')) |
1064 horizontalalignment='right')) |
1062 |
1065 |
1063 # Y coordinate labels are vertical and in figure left side |
1066 # Y coordinate labels are vertical and in figure left side |
1064 self.AxesLabels.append( |
1067 self.AxesLabels.append( |
1065 add_text_func(size='small', rotation='vertical', |
1068 add_text_func(size='small', rotation='vertical', |
1066 verticalalignment='bottom')) |
1069 verticalalignment='bottom')) |
1067 self.Labels.append( |
1070 self.Labels.append( |
1068 add_text_func(size='large', rotation='vertical', |
1071 add_text_func(size='large', rotation='vertical', |
1069 verticalalignment='top')) |
1072 verticalalignment='top')) |
1070 |
1073 |
1071 # Refresh position of labels according to Viewer size |
1074 # Refresh position of labels according to Viewer size |
1072 width, height = self.GetSize() |
1075 width, height = self.GetSize() |
1073 self.RefreshLabelsPosition(height) |
1076 self.RefreshLabelsPosition(height) |
1074 |
1077 |
1075 def RefreshLabelsPosition(self, height): |
1078 def RefreshLabelsPosition(self, height): |
1076 """ |
1079 """ |
1077 Function called when mouse leave Viewer |
1080 Function called when mouse leave Viewer |
1078 @param event: wx.MouseEvent |
1081 @param event: wx.MouseEvent |
1079 """ |
1082 """ |
1081 # canvas size and figure size. As we want that border around figure and |
1084 # canvas size and figure size. As we want that border around figure and |
1082 # text position in figure don't change when canvas size change, we |
1085 # text position in figure don't change when canvas size change, we |
1083 # expressed border and text position in pixel on screen and apply the |
1086 # expressed border and text position in pixel on screen and apply the |
1084 # ratio calculated hereafter to get border and text position in |
1087 # ratio calculated hereafter to get border and text position in |
1085 # matplotlib coordinate |
1088 # matplotlib coordinate |
1086 canvas_ratio = 1. / height # Divide by canvas height in pixel |
1089 canvas_ratio = 1. / height # Divide by canvas height in pixel |
1087 graph_ratio = 1. / ( |
1090 graph_ratio = 1. / ( |
1088 (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) |
1091 (1.0 - (CANVAS_BORDER[0] + CANVAS_BORDER[1]) * canvas_ratio) |
1089 * height) # Divide by figure height in pixel |
1092 * height) # Divide by figure height in pixel |
1090 |
1093 |
1091 # Update position of figure (keeping up and bottom border the same |
1094 # Update position of figure (keeping up and bottom border the same |
1092 # size) |
1095 # size) |
1093 self.Figure.subplotpars.update( |
1096 self.Figure.subplotpars.update( |
1094 top= 1.0 - CANVAS_BORDER[1] * canvas_ratio, |
1097 top=1.0 - CANVAS_BORDER[1] * canvas_ratio, |
1095 bottom= CANVAS_BORDER[0] * canvas_ratio) |
1098 bottom=CANVAS_BORDER[0] * canvas_ratio) |
1096 |
1099 |
1097 # Update position of items labels |
1100 # Update position of items labels |
1098 if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): |
1101 if self.GraphType == GRAPH_PARALLEL or self.Is3DCanvas(): |
1099 num_item = len(self.Items) |
1102 num_item = len(self.Items) |
1100 for idx in xrange(num_item): |
1103 for idx in xrange(num_item): |
1101 |
1104 |
1102 # In 3D graph items variable label are not displayed |
1105 # In 3D graph items variable label are not displayed |
1103 if not self.Is3DCanvas(): |
1106 if not self.Is3DCanvas(): |
1104 # Items variable labels are in figure upper left corner |
1107 # Items variable labels are in figure upper left corner |
1105 self.AxesLabels[idx].set_position( |
1108 self.AxesLabels[idx].set_position( |
1106 (0.05, |
1109 (0.05, |
1107 1.0 - (CANVAS_PADDING + |
1110 1.0 - (CANVAS_PADDING + |
1108 AXES_LABEL_HEIGHT * idx) * graph_ratio)) |
1111 AXES_LABEL_HEIGHT * idx) * graph_ratio)) |
1109 |
1112 |
1110 # Items variable labels are in figure lower right corner |
1113 # Items variable labels are in figure lower right corner |
1111 self.Labels[idx].set_position( |
1114 self.Labels[idx].set_position( |
1112 (0.95, |
1115 (0.95, |
1113 CANVAS_PADDING * graph_ratio + |
1116 CANVAS_PADDING * graph_ratio + |
1114 (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio)) |
1117 (num_item - idx - 1) * VALUE_LABEL_HEIGHT * graph_ratio)) |
1115 else: |
1118 else: |
1116 # X coordinate labels are in figure lower side |
1119 # X coordinate labels are in figure lower side |
1117 self.AxesLabels[0].set_position( |
1120 self.AxesLabels[0].set_position( |
1118 (0.1, CANVAS_PADDING * graph_ratio)) |
1121 (0.1, CANVAS_PADDING * graph_ratio)) |
1119 self.Labels[0].set_position( |
1122 self.Labels[0].set_position( |
1120 (0.95, CANVAS_PADDING * graph_ratio)) |
1123 (0.95, CANVAS_PADDING * graph_ratio)) |
1121 |
1124 |
1122 # Y coordinate labels are vertical and in figure left side |
1125 # Y coordinate labels are vertical and in figure left side |
1123 self.AxesLabels[1].set_position( |
1126 self.AxesLabels[1].set_position( |
1124 (0.05, 2 * CANVAS_PADDING * graph_ratio)) |
1127 (0.05, 2 * CANVAS_PADDING * graph_ratio)) |
1125 self.Labels[1].set_position( |
1128 self.Labels[1].set_position( |
1126 (0.05, 1.0 - CANVAS_PADDING * graph_ratio)) |
1129 (0.05, 1.0 - CANVAS_PADDING * graph_ratio)) |
1127 |
1130 |
1128 # Update subplots |
1131 # Update subplots |
1129 self.Figure.subplots_adjust() |
1132 self.Figure.subplots_adjust() |
1130 |
1133 |
1131 def RefreshViewer(self, refresh_graphics=True): |
1134 def RefreshViewer(self, refresh_graphics=True): |
1132 """ |
1135 """ |
1133 Function called to refresh displayed by matplotlib canvas |
1136 Function called to refresh displayed by matplotlib canvas |
1134 @param refresh_graphics: Flag indicating that graphs have to be |
1137 @param refresh_graphics: Flag indicating that graphs have to be |
1135 refreshed (False: only label values have to be refreshed) |
1138 refreshed (False: only label values have to be refreshed) |
1136 """ |
1139 """ |
1137 # Refresh graphs if needed |
1140 # Refresh graphs if needed |
1138 if refresh_graphics: |
1141 if refresh_graphics: |
1139 # Get tick range of values to display |
1142 # Get tick range of values to display |
1140 start_tick, end_tick = self.ParentWindow.GetRange() |
1143 start_tick, end_tick = self.ParentWindow.GetRange() |
1141 |
1144 |
1142 # Graph is parallel |
1145 # Graph is parallel |
1143 if self.GraphType == GRAPH_PARALLEL: |
1146 if self.GraphType == GRAPH_PARALLEL: |
1144 # Init list of data range for each variable displayed |
1147 # Init list of data range for each variable displayed |
1145 ranges = [] |
1148 ranges = [] |
1146 |
1149 |
1147 # Get data and range for each variable displayed |
1150 # Get data and range for each variable displayed |
1148 for idx, item in enumerate(self.Items): |
1151 for idx, item in enumerate(self.Items): |
1149 data, min_value, max_value = item.GetDataAndValueRange( |
1152 data, min_value, max_value = item.GetDataAndValueRange( |
1150 start_tick, end_tick, not self.ZoomFit) |
1153 start_tick, end_tick, not self.ZoomFit) |
1151 |
1154 |
1152 # Check that data is not empty |
1155 # Check that data is not empty |
1153 if data is not None: |
1156 if data is not None: |
1154 # Add variable range to list of variable data range |
1157 # Add variable range to list of variable data range |
1155 ranges.append((min_value, max_value)) |
1158 ranges.append((min_value, max_value)) |
1156 |
1159 |
1157 # Add plot to canvas if not yet created |
1160 # Add plot to canvas if not yet created |
1158 if len(self.Plots) <= idx: |
1161 if len(self.Plots) <= idx: |
1159 self.Plots.append( |
1162 self.Plots.append( |
1160 self.Axes.plot(data[:, 0], data[:, 1])[0]) |
1163 self.Axes.plot(data[:, 0], data[:, 1])[0]) |
1161 |
1164 |
1162 # Set data to already created plot in canvas |
1165 # Set data to already created plot in canvas |
1163 else: |
1166 else: |
1164 self.Plots[idx].set_data(data[:, 0], data[:, 1]) |
1167 self.Plots[idx].set_data(data[:, 0], data[:, 1]) |
1165 |
1168 |
1166 # Get X and Y axis ranges |
1169 # Get X and Y axis ranges |
1167 x_min, x_max = start_tick, end_tick |
1170 x_min, x_max = start_tick, end_tick |
1168 y_min, y_max = merge_ranges(ranges) |
1171 y_min, y_max = merge_ranges(ranges) |
1169 |
1172 |
1170 # Display cursor in canvas if a cursor tick is defined and it is |
1173 # Display cursor in canvas if a cursor tick is defined and it is |
1171 # include in values tick range |
1174 # include in values tick range |
1172 if (self.CursorTick is not None and |
1175 if self.CursorTick is not None and \ |
1173 start_tick <= self.CursorTick <= end_tick): |
1176 start_tick <= self.CursorTick <= end_tick: |
1174 |
1177 |
1175 # Define a vertical line to display cursor position if no |
1178 # Define a vertical line to display cursor position if no |
1176 # line is already defined |
1179 # line is already defined |
1177 if self.VLine is None: |
1180 if self.VLine is None: |
1178 self.VLine = self.Axes.axvline(self.CursorTick, |
1181 self.VLine = self.Axes.axvline(self.CursorTick, |
1179 color=CURSOR_COLOR) |
1182 color=CURSOR_COLOR) |
1180 |
1183 |
1181 # Set value of vertical line if already defined |
1184 # Set value of vertical line if already defined |
1182 else: |
1185 else: |
1183 self.VLine.set_xdata((self.CursorTick, self.CursorTick)) |
1186 self.VLine.set_xdata((self.CursorTick, self.CursorTick)) |
1184 self.VLine.set_visible(True) |
1187 self.VLine.set_visible(True) |
1185 |
1188 |
1186 # Hide vertical line if cursor tick is not defined or reset |
1189 # Hide vertical line if cursor tick is not defined or reset |
1187 elif self.VLine is not None: |
1190 elif self.VLine is not None: |
1188 self.VLine.set_visible(False) |
1191 self.VLine.set_visible(False) |
1189 |
1192 |
1190 # Graph is orthogonal |
1193 # Graph is orthogonal |
1191 else: |
1194 else: |
1192 # Update tick range, removing ticks that don't have a value for |
1195 # Update tick range, removing ticks that don't have a value for |
1193 # each variable |
1196 # each variable |
1194 start_tick = max(start_tick, self.GetItemsMinCommonTick()) |
1197 start_tick = max(start_tick, self.GetItemsMinCommonTick()) |
1195 end_tick = max(end_tick, start_tick) |
1198 end_tick = max(end_tick, start_tick) |
1196 items = self.ItemsDict.values() |
1199 items = self.ItemsDict.values() |
1197 |
1200 |
1198 # Get data and range for first variable (X coordinate) |
1201 # Get data and range for first variable (X coordinate) |
1199 x_data, x_min, x_max = items[0].GetDataAndValueRange( |
1202 x_data, x_min, x_max = items[0].GetDataAndValueRange( |
1200 start_tick, end_tick, not self.ZoomFit) |
1203 start_tick, end_tick, not self.ZoomFit) |
1201 # Get data and range for second variable (Y coordinate) |
1204 # Get data and range for second variable (Y coordinate) |
1202 y_data, y_min, y_max = items[1].GetDataAndValueRange( |
1205 y_data, y_min, y_max = items[1].GetDataAndValueRange( |
1203 start_tick, end_tick, not self.ZoomFit) |
1206 start_tick, end_tick, not self.ZoomFit) |
1204 |
1207 |
1205 # Normalize X and Y coordinates value range |
1208 # Normalize X and Y coordinates value range |
1206 x_min, x_max = merge_ranges([(x_min, x_max)]) |
1209 x_min, x_max = merge_ranges([(x_min, x_max)]) |
1207 y_min, y_max = merge_ranges([(y_min, y_max)]) |
1210 y_min, y_max = merge_ranges([(y_min, y_max)]) |
1208 |
1211 |
1209 # Get X and Y coordinates for cursor if cursor tick is defined |
1212 # Get X and Y coordinates for cursor if cursor tick is defined |
1210 if self.CursorTick is not None: |
1213 if self.CursorTick is not None: |
1211 x_cursor, x_forced = items[0].GetValue( |
1214 x_cursor, x_forced = items[0].GetValue( |
1212 self.CursorTick, raw=True) |
1215 self.CursorTick, raw=True) |
1213 y_cursor, y_forced = items[1].GetValue( |
1216 y_cursor, y_forced = items[1].GetValue( |
1214 self.CursorTick, raw=True) |
1217 self.CursorTick, raw=True) |
1215 |
1218 |
1216 # Get common data length so that each value has an x and y |
1219 # Get common data length so that each value has an x and y |
1217 # coordinate |
1220 # coordinate |
1218 length = (min(len(x_data), len(y_data)) |
1221 length = (min(len(x_data), len(y_data)) |
1219 if x_data is not None and y_data is not None |
1222 if x_data is not None and y_data is not None |
1220 else 0) |
1223 else 0) |
1221 |
1224 |
1222 # Graph is orthogonal 2D |
1225 # Graph is orthogonal 2D |
1223 if len(self.Items) < 3: |
1226 if len(self.Items) < 3: |
1224 |
1227 |
1225 # Check that x and y data are not empty |
1228 # Check that x and y data are not empty |
1226 if x_data is not None and y_data is not None: |
1229 if x_data is not None and y_data is not None: |
1227 |
1230 |
1228 # Add plot to canvas if not yet created |
1231 # Add plot to canvas if not yet created |
1229 if len(self.Plots) == 0: |
1232 if len(self.Plots) == 0: |
1230 self.Plots.append( |
1233 self.Plots.append( |
1231 self.Axes.plot(x_data[:, 1][:length], |
1234 self.Axes.plot(x_data[:, 1][:length], |
1232 y_data[:, 1][:length])[0]) |
1235 y_data[:, 1][:length])[0]) |
1233 |
1236 |
1234 # Set data to already created plot in canvas |
1237 # Set data to already created plot in canvas |
1235 else: |
1238 else: |
1236 self.Plots[0].set_data( |
1239 self.Plots[0].set_data( |
1237 x_data[:, 1][:length], |
1240 x_data[:, 1][:length], |
1238 y_data[:, 1][:length]) |
1241 y_data[:, 1][:length]) |
1239 |
1242 |
1240 # Display cursor in canvas if a cursor tick is defined and it is |
1243 # Display cursor in canvas if a cursor tick is defined and it is |
1241 # include in values tick range |
1244 # include in values tick range |
1242 if (self.CursorTick is not None and |
1245 if self.CursorTick is not None and \ |
1243 start_tick <= self.CursorTick <= end_tick): |
1246 start_tick <= self.CursorTick <= end_tick: |
1244 |
1247 |
1245 # Define a vertical line to display cursor x coordinate |
1248 # Define a vertical line to display cursor x coordinate |
1246 # if no line is already defined |
1249 # if no line is already defined |
1247 if self.VLine is None: |
1250 if self.VLine is None: |
1248 self.VLine = self.Axes.axvline(x_cursor, |
1251 self.VLine = self.Axes.axvline(x_cursor, |
1249 color=CURSOR_COLOR) |
1252 color=CURSOR_COLOR) |
1250 # Set value of vertical line if already defined |
1253 # Set value of vertical line if already defined |
1251 else: |
1254 else: |
1252 self.VLine.set_xdata((x_cursor, x_cursor)) |
1255 self.VLine.set_xdata((x_cursor, x_cursor)) |
1253 |
1256 |
1254 |
|
1255 # Define a horizontal line to display cursor y |
1257 # Define a horizontal line to display cursor y |
1256 # coordinate if no line is already defined |
1258 # coordinate if no line is already defined |
1257 if self.HLine is None: |
1259 if self.HLine is None: |
1258 self.HLine = self.Axes.axhline(y_cursor, |
1260 self.HLine = self.Axes.axhline(y_cursor, |
1259 color=CURSOR_COLOR) |
1261 color=CURSOR_COLOR) |
1260 # Set value of horizontal line if already defined |
1262 # Set value of horizontal line if already defined |
1261 else: |
1263 else: |
1262 self.HLine.set_ydata((y_cursor, y_cursor)) |
1264 self.HLine.set_ydata((y_cursor, y_cursor)) |
1263 |
1265 |
1264 self.VLine.set_visible(True) |
1266 self.VLine.set_visible(True) |
1265 self.HLine.set_visible(True) |
1267 self.HLine.set_visible(True) |
1266 |
1268 |
1267 # Hide vertical and horizontal line if cursor tick is not |
1269 # Hide vertical and horizontal line if cursor tick is not |
1268 # defined or reset |
1270 # defined or reset |
1269 else: |
1271 else: |
1270 if self.VLine is not None: |
1272 if self.VLine is not None: |
1271 self.VLine.set_visible(False) |
1273 self.VLine.set_visible(False) |
1272 if self.HLine is not None: |
1274 if self.HLine is not None: |
1273 self.HLine.set_visible(False) |
1275 self.HLine.set_visible(False) |
1274 |
1276 |
1275 # Graph is orthogonal 3D |
1277 # Graph is orthogonal 3D |
1276 else: |
1278 else: |
1277 # Remove all plots already defined in 3D canvas |
1279 # Remove all plots already defined in 3D canvas |
1278 while len(self.Axes.lines) > 0: |
1280 while len(self.Axes.lines) > 0: |
1279 self.Axes.lines.pop() |
1281 self.Axes.lines.pop() |
1280 |
1282 |
1281 # Get data and range for third variable (Z coordinate) |
1283 # Get data and range for third variable (Z coordinate) |
1282 z_data, z_min, z_max = items[2].GetDataAndValueRange( |
1284 z_data, z_min, z_max = items[2].GetDataAndValueRange( |
1283 start_tick, end_tick, not self.ZoomFit) |
1285 start_tick, end_tick, not self.ZoomFit) |
1284 |
1286 |
1285 # Normalize Z coordinate value range |
1287 # Normalize Z coordinate value range |
1286 z_min, z_max = merge_ranges([(z_min, z_max)]) |
1288 z_min, z_max = merge_ranges([(z_min, z_max)]) |
1287 |
1289 |
1288 # Check that x, y and z data are not empty |
1290 # Check that x, y and z data are not empty |
1289 if (x_data is not None and y_data is not None and |
1291 if x_data is not None and \ |
1290 z_data is not None): |
1292 y_data is not None and \ |
1291 |
1293 z_data is not None: |
|
1294 |
1292 # Get common data length so that each value has an x, y |
1295 # Get common data length so that each value has an x, y |
1293 # and z coordinate |
1296 # and z coordinate |
1294 length = min(length, len(z_data)) |
1297 length = min(length, len(z_data)) |
1295 |
1298 |
1296 # Add plot to canvas |
1299 # Add plot to canvas |
1297 self.Axes.plot(x_data[:, 1][:length], |
1300 self.Axes.plot(x_data[:, 1][:length], |
1298 y_data[:, 1][:length], |
1301 y_data[:, 1][:length], |
1299 zs = z_data[:, 1][:length]) |
1302 zs=z_data[:, 1][:length]) |
1300 |
1303 |
1301 # Display cursor in canvas if a cursor tick is defined and |
1304 # Display cursor in canvas if a cursor tick is defined and |
1302 # it is include in values tick range |
1305 # it is include in values tick range |
1303 if (self.CursorTick is not None and |
1306 if self.CursorTick is not None and \ |
1304 start_tick <= self.CursorTick <= end_tick): |
1307 start_tick <= self.CursorTick <= end_tick: |
1305 |
1308 |
1306 # Get Z coordinate for cursor |
1309 # Get Z coordinate for cursor |
1307 z_cursor, z_forced = items[2].GetValue( |
1310 z_cursor, z_forced = items[2].GetValue( |
1308 self.CursorTick, raw=True) |
1311 self.CursorTick, raw=True) |
1309 |
1312 |
1310 # Add 3 lines parallel to x, y and z axis to display |
1313 # Add 3 lines parallel to x, y and z axis to display |
1311 # cursor position in 3D |
1314 # cursor position in 3D |
1312 for kwargs in [{"xs": numpy.array([x_min, x_max])}, |
1315 for kwargs in [{"xs": numpy.array([x_min, x_max])}, |
1313 {"ys": numpy.array([y_min, y_max])}, |
1316 {"ys": numpy.array([y_min, y_max])}, |
1314 {"zs": numpy.array([z_min, z_max])}]: |
1317 {"zs": numpy.array([z_min, z_max])}]: |
1317 ("ys", numpy.array([y_cursor, y_cursor])), |
1320 ("ys", numpy.array([y_cursor, y_cursor])), |
1318 ("zs", numpy.array([z_cursor, z_cursor]))]: |
1321 ("zs", numpy.array([z_cursor, z_cursor]))]: |
1319 kwargs.setdefault(param, value) |
1322 kwargs.setdefault(param, value) |
1320 kwargs["color"] = CURSOR_COLOR |
1323 kwargs["color"] = CURSOR_COLOR |
1321 self.Axes.plot(**kwargs) |
1324 self.Axes.plot(**kwargs) |
1322 |
1325 |
1323 # Set Z axis limits |
1326 # Set Z axis limits |
1324 self.Axes.set_zlim(z_min, z_max) |
1327 self.Axes.set_zlim(z_min, z_max) |
1325 |
1328 |
1326 # Set X and Y axis limits |
1329 # Set X and Y axis limits |
1327 self.Axes.set_xlim(x_min, x_max) |
1330 self.Axes.set_xlim(x_min, x_max) |
1328 self.Axes.set_ylim(y_min, y_max) |
1331 self.Axes.set_ylim(y_min, y_max) |
1329 |
1332 |
1330 # Get value and forced flag for each variable displayed in graph |
1333 # Get value and forced flag for each variable displayed in graph |
1331 # If cursor tick is not defined get value and flag of last received |
1334 # If cursor tick is not defined get value and flag of last received |
1332 # or get value and flag of variable at cursor tick |
1335 # or get value and flag of variable at cursor tick |
1333 values, forced = apply(zip, [ |
1336 values, forced = apply(zip, [ |
1334 (item.GetValue(self.CursorTick) |
1337 (item.GetValue(self.CursorTick) |
1335 if self.CursorTick is not None |
1338 if self.CursorTick is not None |
1336 else (item.GetValue(), item.IsForced())) |
1339 else (item.GetValue(), item.IsForced())) |
1337 for item in self.Items]) |
1340 for item in self.Items]) |
1338 |
1341 |
1339 # Get path of each variable displayed simplified using panel variable |
1342 # Get path of each variable displayed simplified using panel variable |
1340 # name mask |
1343 # name mask |
1341 labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask()) |
1344 labels = [item.GetVariable(self.ParentWindow.GetVariableNameMask()) |
1342 for item in self.Items] |
1345 for item in self.Items] |
1343 |
1346 |
1344 # Get style for each variable according to |
1347 # Get style for each variable according to |
1345 styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced) |
1348 styles = map(lambda x: {True: 'italic', False: 'normal'}[x], forced) |
1346 |
1349 |
1347 # Graph is orthogonal 3D, set variables path as 3D axis label |
1350 # Graph is orthogonal 3D, set variables path as 3D axis label |
1348 if self.Is3DCanvas(): |
1351 if self.Is3DCanvas(): |
1349 for idx, label_func in enumerate([self.Axes.set_xlabel, |
1352 for idx, label_func in enumerate([self.Axes.set_xlabel, |
1350 self.Axes.set_ylabel, |
1353 self.Axes.set_ylabel, |
1351 self.Axes.set_zlabel]): |
1354 self.Axes.set_zlabel]): |
1352 label_func(labels[idx], fontdict={'size': 'small', |
1355 label_func(labels[idx], fontdict={'size': 'small', |
1353 'color': COLOR_CYCLE[idx]}) |
1356 'color': COLOR_CYCLE[idx]}) |
1354 |
1357 |
1355 # Graph is not orthogonal 3D, set variables path in axes labels |
1358 # Graph is not orthogonal 3D, set variables path in axes labels |
1356 else: |
1359 else: |
1357 for label, text in zip(self.AxesLabels, labels): |
1360 for label, text in zip(self.AxesLabels, labels): |
1358 label.set_text(text) |
1361 label.set_text(text) |
1359 |
1362 |
1360 # Set value label text and style according to value and forced flag for |
1363 # Set value label text and style according to value and forced flag for |
1361 # each variable displayed |
1364 # each variable displayed |
1362 for label, value, style in zip(self.Labels, values, styles): |
1365 for label, value, style in zip(self.Labels, values, styles): |
1363 label.set_text(value) |
1366 label.set_text(value) |
1364 label.set_style(style) |
1367 label.set_style(style) |
1365 |
1368 |
1366 # Refresh figure |
1369 # Refresh figure |
1367 self.draw() |
1370 self.draw() |
1368 |
1371 |
1369 def draw(self, drawDC=None): |
1372 def draw(self, drawDC=None): |
1370 """ |
1373 """ |
1371 Render the figure. |
1374 Render the figure. |
1372 """ |
1375 """ |
1373 # Render figure using agg |
1376 # Render figure using agg |
1374 FigureCanvasAgg.draw(self) |
1377 FigureCanvasAgg.draw(self) |
1375 |
1378 |
1376 # Get bitmap of figure rendered |
1379 # Get bitmap of figure rendered |
1377 self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) |
1380 self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None) |
1378 if wx.VERSION < (3, 0, 0): |
1381 if wx.VERSION < (3, 0, 0): |
1379 self.bitmap.UseAlpha() |
1382 self.bitmap.UseAlpha() |
1380 |
1383 |
1381 # Create DC for rendering graphics in bitmap |
1384 # Create DC for rendering graphics in bitmap |
1382 destDC = wx.MemoryDC() |
1385 destDC = wx.MemoryDC() |
1383 destDC.SelectObject(self.bitmap) |
1386 destDC.SelectObject(self.bitmap) |
1384 |
1387 |
1385 # Get Graphics Context for DC, for anti-aliased and transparent |
1388 # Get Graphics Context for DC, for anti-aliased and transparent |
1386 # rendering |
1389 # rendering |
1387 destGC = wx.GCDC(destDC) |
1390 destGC = wx.GCDC(destDC) |
1388 |
1391 |
1389 destGC.BeginDrawing() |
1392 destGC.BeginDrawing() |
1390 |
1393 |
1391 # Get canvas size and figure bounding box in canvas |
1394 # Get canvas size and figure bounding box in canvas |
1392 width, height = self.GetSize() |
1395 width, height = self.GetSize() |
1393 bbox = self.GetAxesBoundingBox() |
1396 bbox = self.GetAxesBoundingBox() |
1394 |
1397 |
1395 # If highlight to display is resize, draw thick grey line at bottom |
1398 # If highlight to display is resize, draw thick grey line at bottom |
1396 # side of canvas |
1399 # side of canvas |
1397 if self.Highlight == HIGHLIGHT_RESIZE: |
1400 if self.Highlight == HIGHLIGHT_RESIZE: |
1398 destGC.SetPen(HIGHLIGHT_RESIZE_PEN) |
1401 destGC.SetPen(HIGHLIGHT_RESIZE_PEN) |
1399 destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH) |
1402 destGC.SetBrush(HIGHLIGHT_RESIZE_BRUSH) |
1400 destGC.DrawRectangle(0, height - 5, width, 5) |
1403 destGC.DrawRectangle(0, height - 5, width, 5) |
1401 |
1404 |
1402 # If highlight to display is merging graph, draw 50% transparent blue |
1405 # If highlight to display is merging graph, draw 50% transparent blue |
1403 # rectangle on left or right part of figure depending on highlight type |
1406 # rectangle on left or right part of figure depending on highlight type |
1404 elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]: |
1407 elif self.Highlight in [HIGHLIGHT_LEFT, HIGHLIGHT_RIGHT]: |
1405 destGC.SetPen(HIGHLIGHT_DROP_PEN) |
1408 destGC.SetPen(HIGHLIGHT_DROP_PEN) |
1406 destGC.SetBrush(HIGHLIGHT_DROP_BRUSH) |
1409 destGC.SetBrush(HIGHLIGHT_DROP_BRUSH) |
1407 |
1410 |
1408 x_offset = (bbox.width / 2 |
1411 x_offset = (bbox.width / 2 |
1409 if self.Highlight == HIGHLIGHT_RIGHT |
1412 if self.Highlight == HIGHLIGHT_RIGHT |
1410 else 0) |
1413 else 0) |
1411 destGC.DrawRectangle(bbox.x + x_offset, bbox.y, |
1414 destGC.DrawRectangle(bbox.x + x_offset, bbox.y, |
1412 bbox.width / 2, bbox.height) |
1415 bbox.width / 2, bbox.height) |
1413 |
1416 |
1414 # Draw other Viewer common elements |
1417 # Draw other Viewer common elements |
1415 self.DrawCommonElements(destGC, self.GetButtons()) |
1418 self.DrawCommonElements(destGC, self.GetButtons()) |
1416 |
1419 |
1417 destGC.EndDrawing() |
1420 destGC.EndDrawing() |
1418 |
1421 |
1419 self._isDrawn = True |
1422 self._isDrawn = True |
1420 self.gui_repaint(drawDC=drawDC) |
1423 self.gui_repaint(drawDC=drawDC) |
1421 |
|
1422 |
|