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