1 # Copyright (C) 2016-2023 CEA/DEN, EDF R&D
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 # License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
8 # This library is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # Lesser General Public License for more details.
13 # You should have received a copy of the GNU Lesser General Public
14 # License along with this library; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 import matplotlib.pyplot as plt
21 import matplotlib.colors as colors
22 from pyqtside import QtWidgets, QtCore
23 from pyqtside.QtCore import QObject
24 from matplotlib.figure import Figure
25 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT
27 from .View import View
28 from .CurveView import CurveView
29 from .PlotWidget import PlotWidget
30 from .PlotSettings import PlotSettings
31 from .utils import Logger, trQ
33 class EventHandler(QObject):
34 """ Handle the right-click properly so that it only triggers the contextual menu """
35 def __init__(self,parent=None):
36 QObject.__init__(self, parent)
38 def eventFilter(self, obj, event):
39 if event.type() == QtCore.QEvent.MouseButtonPress:
40 if event.button() == 2:
41 # Discarding right button press to only keep context menu display
42 return True # Event handled (and hence not passed to matplotlib)
43 return QObject.eventFilter(self, obj, event)
46 AUTOFIT_MARGIN = 0.03 # 3%
48 # See http://matplotlib.org/api/markers_api.html:
49 CURVE_MARKERS = [ "o" ,# circle
71 _DEFAULT_LEGEND_STATE = False # for test purposes mainly - initial status of the legend
73 def __init__(self, controller):
74 View.__init__(self, controller)
75 self._eventHandler = EventHandler()
77 self._curveViews = {} # key: curve (model) ID, value: CurveView
78 self._salomeViewID = None
79 self._mplFigure = None
81 self._mplCanvas = None
82 self._plotWidget = None
83 self._sgPyQt = self._controller._sgPyQt
85 self._mplNavigationActions = {}
86 self._toobarMPL = None
88 self._currCrv = None # current curve selected in the view
91 self._legendLoc = "right" # "right" or "bottom"
95 self._dragOnDrop = False
101 self._defaultLineStyle = None
102 self._last_point = None
103 self._lastMarkerID = -1
104 self._blockLogSignal = False
106 self._axisXSciNotation = False
107 self._axisYSciNotation = False
108 self._prevTitle = None
110 def __repaintOK(self):
111 """ To be called inside XYView each time a low-level expansive matplotlib methods is to be invoked.
112 @return False if painting is currently locked, in which case it will also register the current XYView
113 as needing a refresh when unlocked
115 ret = self._controller._plotManager.isRepaintLocked()
117 self._controller._plotManager.registerRepaint(self._model)
120 def appendCurve(self, curveID):
121 newC = CurveView(self._controller, self)
122 newC.setModel(self._model._curves[curveID])
123 newC.setMPLAxes(self._mplAxes)
125 newC.setMarker(self.getMarker(go_next=True))
126 self._curveViews[curveID] = newC
128 def removeCurve(self, curveID):
129 v = self._curveViews.pop(curveID)
131 if self._currCrv is not None and self._currCrv.getID() == curveID:
134 def cleanBeforeClose(self):
135 """ Clean some items to avoid accumulating stuff in memory """
136 self._mplFigure.clear()
137 plt.close(self._mplFigure)
138 self._plotWidget.clearAll()
139 # For memory debugging only:
144 if self.__repaintOK():
145 Logger.Debug("XYView::draw")
146 self._mplCanvas.draw()
148 def onXLabelChange(self):
149 if self.__repaintOK():
150 self._mplAxes.set_xlabel(self._model._xlabel)
153 def onYLabelChange(self):
154 if self.__repaintOK():
155 self._mplAxes.set_ylabel(self._model._ylabel)
158 def onTitleChange(self):
159 if self.__repaintOK():
160 self._mplAxes.set_title(self._model._title)
161 self.updateViewTitle()
164 def onCurveTitleChange(self):
165 # Updating the legend should suffice
166 self.showHideLegend()
168 def onClearAll(self):
169 """ Just does an update with a reset of the marker cycle. """
170 if self.__repaintOK():
171 self._lastMarkerID = -1
174 def onPick(self, event):
175 """ MPL callback when picking
177 if event.mouseevent.button == 1:
180 for crv_id, cv in list(self._curveViews.items()):
181 if cv._mplLines[0] is a:
183 # Use the plotmanager so that other plot sets get their current reset:
184 self._controller._plotManager.setCurrentCurve(selected_id)
186 def createAndAddLocalAction(self, icon_file, short_name):
187 return self._toolbar.addAction(self._sgPyQt.loadIcon("CURVEPLOT", icon_file), short_name)
189 def createPlotWidget(self):
190 self._mplFigure = Figure((8.0,5.0), dpi=100)
191 self._mplCanvas = FigureCanvasQTAgg(self._mplFigure)
192 self._mplCanvas.installEventFilter(self._eventHandler)
193 self._mplCanvas.mpl_connect('pick_event', self.onPick)
194 self._mplAxes = self._mplFigure.add_subplot(1, 1, 1)
195 self._plotWidget = PlotWidget()
196 self._toobarMPL = NavigationToolbar2QT(self._mplCanvas, None)
197 for act in self._toobarMPL.actions():
198 actionName = str(act.text()).strip()
199 self._mplNavigationActions[actionName] = act
200 self._plotWidget.setCentralWidget(self._mplCanvas)
201 self._toolbar = self._plotWidget.toolBar
202 self.populateToolbar()
204 self._popupMenu = QtWidgets.QMenu()
205 self._popupMenu.addAction(self._actionLegend)
207 # Connect evenement for the graphic scene
208 self._mplCanvas.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
209 self._mplCanvas.customContextMenuRequested.connect(self.onContextMenu)
210 self._mplCanvas.mpl_connect('scroll_event', self.onScroll)
211 self._mplCanvas.mpl_connect('button_press_event', self.onMousePress)
213 def populateToolbar(self):
214 # Action to dump view in a file
215 a = self.createAndAddLocalAction("dump_view.png", trQ("DUMP_VIEW_TXT"))
216 a.triggered.connect(self.dumpView)
217 self._toolbar.addSeparator()
218 # Actions to manipulate the scene
219 a = self.createAndAddLocalAction("fit_all.png", trQ("FIT_ALL_TXT"))
220 a.triggered.connect(self.autoFit)
221 # Zoom and pan are mutually exclusive but can be both de-activated:
222 self._zoomAction = self.createAndAddLocalAction("fit_area.png", trQ("FIT_AREA_TXT"))
223 self._zoomAction.triggered.connect(self.zoomArea)
224 self._zoomAction.setCheckable(True)
225 self._panAction = self.createAndAddLocalAction("zoom_pan.png", trQ("ZOOM_PAN_TXT"))
226 self._panAction.triggered.connect(self.pan)
227 self._panAction.setCheckable(True)
228 self._toolbar.addSeparator()
229 # Actions to change the representation of curves
230 self._curveActionGroup = QtWidgets.QActionGroup(self._plotWidget)
231 self._pointsAction = self.createAndAddLocalAction("draw_points.png", trQ("DRAW_POINTS_TXT"))
232 self._pointsAction.setCheckable(True)
233 self._linesAction = self.createAndAddLocalAction("draw_lines.png", trQ("DRAW_LINES_TXT"))
234 self._linesAction.setCheckable(True)
235 self._curveActionGroup.addAction(self._pointsAction)
236 self._curveActionGroup.addAction(self._linesAction)
237 self._linesAction.setChecked(True)
238 self._curveActionGroup.triggered.connect(self.changeModeCurve)
239 self._curveActionGroup.setExclusive(True)
240 self._toolbar.addSeparator()
241 # Actions to draw horizontal curves as linear or logarithmic
242 self._horActionGroup = QtWidgets.QActionGroup(self._plotWidget)
243 self._horLinearAction = self.createAndAddLocalAction("hor_linear.png", trQ("HOR_LINEAR_TXT"))
244 self._horLinearAction.setCheckable(True)
245 self._horLogarithmicAction = self.createAndAddLocalAction("hor_logarithmic.png", trQ("HOR_LOGARITHMIC_TXT"))
246 self._horLogarithmicAction.setCheckable(True)
247 self._horActionGroup.addAction(self._horLinearAction)
248 self._horActionGroup.addAction(self._horLogarithmicAction)
249 self._horLinearAction.setChecked(True)
250 self._horActionGroup.triggered.connect(self.onViewHorizontalMode)
251 self._toolbar.addSeparator()
252 # Actions to draw vertical curves as linear or logarithmic
253 self._verActionGroup = QtWidgets.QActionGroup(self._plotWidget)
254 self._verLinearAction = self.createAndAddLocalAction("ver_linear.png", trQ("VER_LINEAR_TXT"))
255 self._verLinearAction.setCheckable(True)
256 self._verLogarithmicAction = self.createAndAddLocalAction("ver_logarithmic.png", trQ("VER_LOGARITHMIC_TXT"))
257 self._verLogarithmicAction.setCheckable(True)
258 self._verActionGroup.addAction(self._verLinearAction)
259 self._verActionGroup.addAction(self._verLogarithmicAction)
260 self._verLinearAction.setChecked(True)
261 self._verActionGroup.triggered.connect(self.onViewVerticalMode)
262 self._verActionGroup.setExclusive(True)
263 self._toolbar.addSeparator()
264 # Action to show or hide the legend
265 self._actionLegend = self.createAndAddLocalAction("legend.png", trQ("SHOW_LEGEND_TXT"))
266 self._actionLegend.setCheckable(True)
267 self._actionLegend.triggered.connect(self.showHideLegend)
268 if self._DEFAULT_LEGEND_STATE:
269 self._actionLegend.setChecked(True)
270 self._toolbar.addSeparator()
271 # Action to set the preferences
272 a = self.createAndAddLocalAction("settings.png", trQ("SETTINGS_TXT"))
273 a.triggered.connect(self.onSettings)
277 # Choice of the view backup file
279 for form in ["IMAGES_FILES", "PDF_FILES", "POSTSCRIPT_FILES", "ENCAPSULATED_POSTSCRIPT_FILES"]:
280 filters.append(trQ(form))
281 fileName = self._sgPyQt.getFileName(self._sgPyQt.getDesktop(),
284 trQ("DUMP_VIEW_FILE"),
288 self._mplAxes.figure.savefig(name)
291 def autoFit(self, check=True, repaint=True):
293 if self.__repaintOK():
294 self._mplAxes.relim()
295 xm, xM = self._mplAxes.xaxis.get_data_interval()
296 ym, yM = self._mplAxes.yaxis.get_data_interval()
298 if np.isfinite(xm) and np.isfinite(xM) and np.isfinite(ym) and np.isfinite(yM):
299 self._mplAxes.axis([xm, xM, ym-i*self.AUTOFIT_MARGIN, yM+i*self.AUTOFIT_MARGIN])
304 if self._panAction.isChecked() and self._zoomAction.isChecked():
305 self._panAction.setChecked(False)
306 # Trigger underlying matplotlib action:
307 self._mplNavigationActions["Zoom"].trigger()
310 if self._panAction.isChecked() and self._zoomAction.isChecked():
311 self._zoomAction.setChecked(False)
312 # Trigger underlying matplotlib action:
313 self._mplNavigationActions["Pan"].trigger()
315 def getMarker(self, go_next=False):
317 self._lastMarkerID = (self._lastMarkerID+1) % len(self.CURVE_MARKERS)
318 return self.CURVE_MARKERS[self._lastMarkerID]
320 def changeModeCurve(self, repaint=True):
321 if not self.__repaintOK():
323 action = self._curveActionGroup.checkedAction()
324 if action is self._pointsAction :
325 for crv_view in list(self._curveViews.values()):
326 crv_view.setLineStyle("None")
327 elif action is self._linesAction :
328 for crv_view in list(self._curveViews.values()):
329 crv_view.setLineStyle("-")
331 raise NotImplementedError
335 def setXLog(self, log, repaint=True):
336 if not self.__repaintOK():
338 self._blockLogSignal = True
340 self._mplAxes.set_xscale('log')
341 self._horLogarithmicAction.setChecked(True)
343 self._mplAxes.set_xscale('linear')
344 self._horLinearAction.setChecked(True)
348 self._blockLogSignal = False
350 def setYLog(self, log, repaint=True):
351 if not self.__repaintOK():
353 self._blockLogSignal = True
355 self._mplAxes.set_yscale('log')
356 self._verLogarithmicAction.setChecked(True)
358 self._mplAxes.set_yscale('linear')
359 self._verLinearAction.setChecked(True)
363 self._blockLogSignal = False
365 def setXSciNotation(self, sciNotation, repaint=True):
366 self._axisXSciNotation = sciNotation
367 self.changeFormatAxis()
371 def setYSciNotation(self, sciNotation, repaint=True):
372 self._axisYSciNotation = sciNotation
373 self.changeFormatAxis()
377 def onViewHorizontalMode(self, checked=True, repaint=True):
378 if self._blockLogSignal:
380 action = self._horActionGroup.checkedAction()
381 if action is self._horLinearAction:
382 self.setXLog(False, repaint)
383 elif action is self._horLogarithmicAction:
384 self.setXLog(True, repaint)
386 raise NotImplementedError
388 def onViewVerticalMode(self, checked=True, repaint=True):
389 if self._blockLogSignal:
391 action = self._verActionGroup.checkedAction()
392 if action is self._verLinearAction:
393 self.setYLog(False, repaint)
394 elif action is self._verLogarithmicAction:
395 self.setYLog(True, repaint)
397 raise NotImplementedError
401 def __adjustFigureMargins(self, withLegend):
402 """ Adjust figure margins to make room for the legend """
405 bbox = leg.get_window_extent()
406 # In axes coordinates:
407 bbox2 = bbox.transformed(leg.figure.transFigure.inverted())
408 if self._legendLoc == "right":
409 self._mplFigure.subplots_adjust(right=1.0-(bbox2.width+0.02))
410 elif self._legendLoc == "bottom":
411 self._mplFigure.subplots_adjust(bottom=bbox2.height+0.1)
413 # Reset to default (rc) values
414 self._mplFigure.subplots_adjust(bottom=0.1, right=0.9)
416 def setLegendVisible(self, visible, repaint=True):
417 if visible and not self._actionLegend.isChecked():
418 self._actionLegend.setChecked(True)
419 self.showHideLegend(repaint=repaint)
420 if not visible and self._actionLegend.isChecked():
421 self._actionLegend.setChecked(False)
422 self.showHideLegend(repaint=repaint)
424 def showHideLegend(self, actionChecked=None, repaint=True):
425 if not self.__repaintOK(): # Show/hide legend is extremely costly
428 show = self._actionLegend.isChecked()
429 nCurves = len(self._curveViews)
430 if nCurves > 10: fontSize = 'x-small'
431 else: fontSize = None
435 leg = self._mplAxes.legend()
436 if leg is not None: leg.remove()
437 if show and nCurves > 0:
438 # Recreate legend from scratch
439 if self._legend is not None:
441 self._mplAxes._legend = None
442 if self._legendLoc == "bottom":
443 self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(0.0, -0.05, 1.0, -0.05),
444 borderaxespad=0.0, mode="expand", fancybox=True,
445 shadow=True, ncol=3, prop={'size':fontSize, 'style': 'italic'})
446 elif self._legendLoc == "right":
447 self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(1.02,1.0), borderaxespad=0.0,
448 ncol=1, fancybox=True, shadow=True, prop={'size':fontSize, 'style': 'italic'})
450 raise Exception("Invalid legend placement! Must be 'bottom' or 'right'")
451 # Canvas must be drawn so we can adjust the figure placement:
452 self._mplCanvas.draw()
453 self.__adjustFigureMargins(withLegend=True)
455 if self._legend is None:
459 self._legend.set_visible(False)
461 self._mplAxes._legend = None
462 self._mplCanvas.draw()
463 self.__adjustFigureMargins(withLegend=False)
465 curr_crv = self._model._currentCurve
466 if curr_crv is None: curr_title = None
467 else: curr_title = curr_crv.getTitle()
468 if self._legend is not None:
469 for label in self._legend.get_texts() :
470 text = label.get_text()
471 if (text == curr_title):
472 label.set_backgroundcolor('0.85')
474 label.set_backgroundcolor('white')
479 def onSettings(self, trigger=False, dlg_test=None):
480 dlg = dlg_test or PlotSettings()
481 dlg.titleEdit.setText(self._mplAxes.get_title())
482 dlg.axisXTitleEdit.setText(self._mplAxes.get_xlabel())
483 dlg.axisYTitleEdit.setText(self._mplAxes.get_ylabel())
484 dlg.gridCheckBox.setChecked(self._mplAxes.xaxis._gridOnMajor) # could not find a relevant API to check this
485 dlg.axisXSciCheckBox.setChecked(self._axisXSciNotation)
486 dlg.axisYSciCheckBox.setChecked(self._axisYSciNotation)
487 xmin, xmax = self._mplAxes.get_xlim()
488 ymin, ymax = self._mplAxes.get_ylim()
489 xminText = "%g" %xmin
490 xmaxText = "%g" %xmax
491 yminText = "%g" %ymin
492 ymaxText = "%g" %ymax
493 dlg.axisXMinEdit.setText(xminText)
494 dlg.axisXMaxEdit.setText(xmaxText)
495 dlg.axisYMinEdit.setText(yminText)
496 dlg.axisYMaxEdit.setText(ymaxText)
498 dlg.markerCurve.clear()
499 for marker in self.CURVE_MARKERS :
500 dlg.markerCurve.addItem(marker)
501 curr_crv = self._model.getCurrentCurve()
502 if not curr_crv is None:
503 dlg.colorCurve.setEnabled(True)
504 dlg.markerCurve.setEnabled(True)
505 name = curr_crv.getTitle()
506 dlg.setSelectedCurveName(name)
507 view = self._curveViews[curr_crv.getID()]
508 marker = view.getMarker()
509 color = view.getColor()
510 index = dlg.markerCurve.findText(marker)
511 dlg.markerCurve.setCurrentIndex(index)
512 rgb = colors.colorConverter.to_rgb(color)
513 dlg.setRGB(rgb[0],rgb[1],rgb[2])
515 dlg.colorCurve.setEnabled(False)
516 dlg.markerCurve.setEnabled(False)
517 dlg.setSelectedCurveName("")
519 if self._legend is None:
520 dlg.showLegendCheckBox.setChecked(False)
521 dlg.legendPositionComboBox.setEnabled(False)
523 if self._legend.get_visible():
524 dlg.showLegendCheckBox.setChecked(True)
525 dlg.legendPositionComboBox.setEnabled(True)
526 if self._legendLoc == "bottom":
527 dlg.legendPositionComboBox.setCurrentIndex(0)
528 elif self._legendLoc == "right" :
529 dlg.legendPositionComboBox.setCurrentIndex(1)
531 dlg.showLegendCheckBox.setChecked(False)
532 dlg.legendPositionComboBox.setEnabled(False)
536 self._model.setTitle(dlg.titleEdit.text())
538 self._model.setXLabel(dlg.axisXTitleEdit.text())
539 self._model.setYLabel(dlg.axisYTitleEdit.text())
541 if dlg.gridCheckBox.isChecked() :
542 self._mplAxes.grid(True)
544 self._mplAxes.grid(False)
546 if dlg.showLegendCheckBox.isChecked():
547 self._actionLegend.setChecked(True)
548 if dlg.legendPositionComboBox.currentIndex() == 0 :
549 self._legendLoc = "bottom"
550 elif dlg.legendPositionComboBox.currentIndex() == 1 :
551 self._legendLoc = "right"
553 self._actionLegend.setChecked(False)
554 xminText = dlg.axisXMinEdit.text()
555 xmaxText = dlg.axisXMaxEdit.text()
556 yminText = dlg.axisYMinEdit.text()
557 ymaxText = dlg.axisYMaxEdit.text()
558 self._mplAxes.axis([float(xminText), float(xmaxText), float(yminText), float(ymaxText)] )
559 self._axisXSciNotation = dlg.axisXSciCheckBox.isChecked()
560 self._axisYSciNotation = dlg.axisYSciCheckBox.isChecked()
561 self.changeFormatAxis()
562 # Color and marker of the curve
564 view.setColor(dlg.getRGB())
565 view.setMarker(self.CURVE_MARKERS[dlg.markerCurve.currentIndex()])
566 crvModel = view._model
567 if dlg.nameCurve.text() != crvModel.getTitle():
568 Logger.Debug("XYView : about to cahnge crv title after settings")
569 view._model.setTitle(dlg.nameCurve.text())
570 self.showHideLegend(repaint=True)
571 self._mplCanvas.draw()
574 def updateViewTitle(self):
576 if self._model._title != "":
577 s = " - %s" % self._model._title
578 title = "CurvePlot (%d)%s" % (self._model.getID(), s)
579 self._sgPyQt.setViewTitle(self._salomeViewID, title)
581 def onCurrentPlotSetChange(self):
582 """ Avoid a unnecessary call to update() when just switching current plot set! """
585 def onCurrentCurveChange(self):
586 Logger.Debug("XYView::onCurrentCurveChange()")
587 curr_crv2 = self._model.getCurrentCurve()
588 if curr_crv2 != self._currCrv:
589 if self._currCrv is not None:
590 view = self._curveViews[self._currCrv.getID()]
591 view.toggleHighlight(False)
592 if not curr_crv2 is None:
593 view = self._curveViews[curr_crv2.getID()]
594 view.toggleHighlight(True)
595 self._currCrv = curr_crv2
596 self.showHideLegend(repaint=False) # redo legend
599 def changeFormatAxis(self) :
600 if not self.__repaintOK():
603 # don't try to switch to sci notation if we are not using the
604 # matplotlib.ticker.ScalarFormatter (i.e. if in Log for ex.)
605 if self._horLinearAction.isChecked():
606 if self._axisXSciNotation :
607 self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='x')
609 self._mplAxes.ticklabel_format(style='plain',axis='x')
610 if self._verLinearAction.isChecked():
611 if self._axisYSciNotation :
612 self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='y')
614 self._mplAxes.ticklabel_format(style='plain',axis='y')
617 if self._salomeViewID is None:
618 self.createPlotWidget()
619 self._salomeViewID = self._sgPyQt.createView("CurvePlot", self._plotWidget)
620 Logger.Debug("Creating SALOME view ID=%d" % self._salomeViewID)
621 self._sgPyQt.setViewVisible(self._salomeViewID, True)
623 self.updateViewTitle()
625 # Check list of curve views:
626 set_mod = set(self._model._curves.keys())
627 set_view = set(self._curveViews.keys())
629 # Deleted/Added curves:
630 dels = set_view - set_mod
631 added = set_mod - set_view
636 if not len(self._curveViews):
638 self._mplAxes.set_prop_cycle(None)
643 # Axes labels and title
644 self._mplAxes.set_xlabel(self._model._xlabel)
645 self._mplAxes.set_ylabel(self._model._ylabel)
646 self._mplAxes.set_title(self._model._title)
648 self.onViewHorizontalMode(repaint=False)
649 self.onViewVerticalMode(repaint=False)
650 self.changeModeCurve(repaint=False)
651 self.showHideLegend(repaint=False) # The canvas is repainted anyway (needed to get legend bounding box)
652 self.changeFormatAxis()
655 if len(self._curveViews):
656 self.autoFit(repaint=False)
659 def onDataChange(self):
660 # the rest is done in the CurveView:
661 self.autoFit(repaint=True)
663 def onMousePress(self, event):
664 if event.button == 3 :
665 if self._panAction.isChecked():
666 self._panAction.setChecked(False)
667 if self._zoomAction.isChecked():
668 self._zoomAction.setChecked(False)
670 def onContextMenu(self, position):
671 pos = self._mplCanvas.mapToGlobal(QtCore.QPoint(position.x(),position.y()))
672 self._popupMenu.exec_(pos)
674 def onScroll(self, event):
675 # Event location (x and y)
679 cur_xlim = self._mplAxes.get_xlim()
680 cur_ylim = self._mplAxes.get_ylim()
683 if event.button == 'down':
685 scale_factor = 1 / base_scale
686 elif event.button == 'up':
688 scale_factor = base_scale
690 # deal with something that should never happen
693 new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
694 new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
696 relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
697 rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
699 self._mplAxes.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
700 self._mplAxes.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
705 def onPressEvent(self, event):
706 if event.button == 3 :
707 #self._mplCanvas.emit(QtCore.SIGNAL("button_release_event()"))
708 canvasSize = event.canvas.geometry()
709 point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
710 self._popupMenu.exec_(point)
712 print("Press event on the other button")
713 #if event.button == 3 :
714 # canvasSize = event.canvas.geometry()
715 # point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
716 # self._popupMenu.move(point)
717 # self._popupMenu.show()
719 def onMotionEvent(self, event):
720 print("OnMotionEvent ",event.button)
721 #if event.button == 3 :
722 # event.button = None
725 def onReleaseEvent(self, event):
726 print("OnReleaseEvent ",event.button)
727 #if event.button == 3 :
728 # event.button = None