Salome HOME
Copyright update 2022
[modules/gui.git] / tools / CurvePlot / src / python / views / XYView.py
1 # Copyright (C) 2016-2022  CEA/DEN, EDF R&D
2 #
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.
7 #
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.
12 #
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
16 #
17 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 #
19
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
26
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
32
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)
37
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)
44
45 class XYView(View):
46   AUTOFIT_MARGIN = 0.03  # 3%
47
48   # See http://matplotlib.org/api/markers_api.html:
49   CURVE_MARKERS = [ "o" ,#  circle
50                     "*",  # star
51                     "+",  # plus
52                     "x",  # x
53                     "s",  # square
54                     "p",  # pentagon
55                     "h",  # hexagon1
56                     "8",  # octagon
57                     "D",  # diamond
58                     "^",  # triangle_up
59                     "<",  # triangle_left
60                     ">",  # triangle_right
61                     "1",  # tri_down
62                     "2",  # tri_up
63                     "3",  # tri_left
64                     "4",  # tri_right
65                     "v",  # triangle_down
66                     "H",  # hexagon2
67                     "d",  # thin diamond
68                     "",   # NO MARKER
69                    ]
70
71   _DEFAULT_LEGEND_STATE = False   # for test purposes mainly - initial status of the legend
72
73   def __init__(self, controller):
74     View.__init__(self, controller)
75     self._eventHandler = EventHandler()
76
77     self._curveViews = {}    # key: curve (model) ID, value: CurveView
78     self._salomeViewID = None
79     self._mplFigure = None
80     self._mplAxes = None
81     self._mplCanvas = None
82     self._plotWidget = None
83     self._sgPyQt = self._controller._sgPyQt
84     self._toolbar = None
85     self._mplNavigationActions = {}
86     self._toobarMPL = None
87     self._grid = None
88     self._currCrv = None   # current curve selected in the view
89
90     self._legend = None
91     self._legendLoc = "right"  # "right" or "bottom"
92
93     self._fitArea = False
94     self._zoomPan = False
95     self._dragOnDrop = False
96     self._move = False
97
98     self._patch = None
99     self._xdata = None
100     self._ydata = None
101     self._defaultLineStyle = None
102     self._last_point = None
103     self._lastMarkerID = -1
104     self._blockLogSignal = False
105
106     self._axisXSciNotation = False
107     self._axisYSciNotation = False
108     self._prevTitle = None
109
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
114     """
115     ret = self._controller._plotManager.isRepaintLocked()
116     if ret:
117       self._controller._plotManager.registerRepaint(self._model)
118     return (not ret)
119
120   def appendCurve(self, curveID):
121     newC = CurveView(self._controller, self)
122     newC.setModel(self._model._curves[curveID])
123     newC.setMPLAxes(self._mplAxes)
124     newC.draw()
125     newC.setMarker(self.getMarker(go_next=True))
126     self._curveViews[curveID] = newC
127
128   def removeCurve(self, curveID):
129     v = self._curveViews.pop(curveID)
130     v.erase()
131     if self._currCrv is not None and self._currCrv.getID() == curveID:
132       self._currCrv = None
133
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:
140     import gc
141     gc.collect()
142
143   def repaint(self):
144     if self.__repaintOK():
145       Logger.Debug("XYView::draw")
146       self._mplCanvas.draw()
147
148   def onXLabelChange(self):
149     if self.__repaintOK():
150       self._mplAxes.set_xlabel(self._model._xlabel)
151       self.repaint()
152
153   def onYLabelChange(self):
154     if self.__repaintOK():
155       self._mplAxes.set_ylabel(self._model._ylabel)
156       self.repaint()
157
158   def onTitleChange(self):
159     if self.__repaintOK():
160       self._mplAxes.set_title(self._model._title)
161       self.updateViewTitle()
162       self.repaint()
163
164   def onCurveTitleChange(self):
165     # Updating the legend should suffice
166     self.showHideLegend()
167
168   def onClearAll(self):
169     """ Just does an update with a reset of the marker cycle. """
170     if self.__repaintOK():
171       self._lastMarkerID = -1
172       self.update()
173
174   def onPick(self, event):
175     """ MPL callback when picking
176     """
177     if event.mouseevent.button == 1:
178       selected_id = -1
179       a = event.artist
180       for crv_id, cv in list(self._curveViews.items()):
181         if cv._mplLines[0] is a:
182           selected_id = crv_id
183       # Use the plotmanager so that other plot sets get their current reset:
184       self._controller._plotManager.setCurrentCurve(selected_id)
185
186   def createAndAddLocalAction(self, icon_file, short_name):
187     return self._toolbar.addAction(self._sgPyQt.loadIcon("CURVEPLOT", icon_file), short_name)
188
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()
203
204     self._popupMenu = QtWidgets.QMenu()
205     self._popupMenu.addAction(self._actionLegend)
206
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)
212
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)
274     pass
275
276   def dumpView(self):
277     # Choice of the view backup file
278     filters = []
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(),
282                                         "",
283                                         filters,
284                                         trQ("DUMP_VIEW_FILE"),
285                                         False )
286     name = str(fileName)
287     if name != "":
288       self._mplAxes.figure.savefig(name)
289     pass
290
291   def autoFit(self, check=True, repaint=True):
292     import numpy as np
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()
297       i = yM-ym
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])
300       if repaint:
301         self.repaint()
302
303   def zoomArea(self):
304     if self._panAction.isChecked() and self._zoomAction.isChecked():
305       self._panAction.setChecked(False)
306     # Trigger underlying matplotlib action:
307     self._mplNavigationActions["Zoom"].trigger()
308
309   def pan(self):
310     if self._panAction.isChecked() and self._zoomAction.isChecked():
311       self._zoomAction.setChecked(False)
312     # Trigger underlying matplotlib action:
313     self._mplNavigationActions["Pan"].trigger()
314
315   def getMarker(self, go_next=False):
316     if go_next:
317       self._lastMarkerID = (self._lastMarkerID+1) % len(self.CURVE_MARKERS)
318     return self.CURVE_MARKERS[self._lastMarkerID]
319
320   def changeModeCurve(self, repaint=True):
321     if not self.__repaintOK():
322       return
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("-")
330     else :
331       raise NotImplementedError
332     if repaint:
333       self.repaint()
334
335   def setXLog(self, log, repaint=True):
336     if not self.__repaintOK():
337       return
338     self._blockLogSignal = True
339     if log:
340       self._mplAxes.set_xscale('log')
341       self._horLogarithmicAction.setChecked(True)
342     else:
343       self._mplAxes.set_xscale('linear')
344       self._horLinearAction.setChecked(True)
345     if repaint:
346       self.autoFit()
347       self.repaint()
348     self._blockLogSignal = False
349
350   def setYLog(self, log, repaint=True):
351     if not self.__repaintOK():
352       return
353     self._blockLogSignal = True
354     if log:
355       self._mplAxes.set_yscale('log')
356       self._verLogarithmicAction.setChecked(True)
357     else:
358       self._mplAxes.set_yscale('linear')
359       self._verLinearAction.setChecked(True)
360     if repaint:
361       self.autoFit()
362       self.repaint()
363     self._blockLogSignal = False
364
365   def setXSciNotation(self, sciNotation, repaint=True):
366     self._axisXSciNotation = sciNotation
367     self.changeFormatAxis()
368     if repaint:
369       self.repaint()
370
371   def setYSciNotation(self, sciNotation, repaint=True):
372     self._axisYSciNotation = sciNotation
373     self.changeFormatAxis()
374     if repaint:
375       self.repaint()
376
377   def onViewHorizontalMode(self, checked=True, repaint=True):
378     if self._blockLogSignal:
379       return
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)
385     else:
386       raise NotImplementedError
387
388   def onViewVerticalMode(self, checked=True, repaint=True):
389     if self._blockLogSignal:
390       return
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)
396     else:
397       raise NotImplementedError
398     if repaint:
399       self.repaint()
400
401   def __adjustFigureMargins(self, withLegend):
402     """ Adjust figure margins to make room for the legend """
403     if withLegend:
404       leg = self._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)
412     else:
413       # Reset to default (rc) values
414       self._mplFigure.subplots_adjust(bottom=0.1, right=0.9)
415
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)
423
424   def showHideLegend(self, actionChecked=None, repaint=True):
425     if not self.__repaintOK():  # Show/hide legend is extremely costly
426       return
427
428     show = self._actionLegend.isChecked()
429     nCurves = len(self._curveViews)
430     if nCurves > 10: fontSize = 'x-small'
431     else:            fontSize = None
432
433     if nCurves == 0:
434       # Remove legend
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:
440         self._legend = 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'})
449       else:
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)
454     else:
455       if self._legend is None:
456         # Nothing to do
457         return
458       else:
459         self._legend.set_visible(False)
460         self._legend = None
461         self._mplAxes._legend = None
462         self._mplCanvas.draw()
463         self.__adjustFigureMargins(withLegend=False)
464
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')
473         else :
474           label.set_backgroundcolor('white')
475
476     if repaint:
477       self.repaint()
478
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)
497     # List of markers
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])
514     else :
515       dlg.colorCurve.setEnabled(False)
516       dlg.markerCurve.setEnabled(False)
517       dlg.setSelectedCurveName("")
518       view = None
519     if self._legend is None:
520       dlg.showLegendCheckBox.setChecked(False)
521       dlg.legendPositionComboBox.setEnabled(False)
522     else :
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)
530       else :
531         dlg.showLegendCheckBox.setChecked(False)
532         dlg.legendPositionComboBox.setEnabled(False)
533
534     if dlg.exec_():
535       # Title
536       self._model.setTitle(dlg.titleEdit.text())
537       # Axis
538       self._model.setXLabel(dlg.axisXTitleEdit.text())
539       self._model.setYLabel(dlg.axisYTitleEdit.text())
540       # Grid
541       if dlg.gridCheckBox.isChecked() :
542         self._mplAxes.grid(True)
543       else :
544         self._mplAxes.grid(False)
545       # Legend
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"
552       else :
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
563       if view:
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()
572     pass
573
574   def updateViewTitle(self):
575     s = ""
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)
580
581   def onCurrentPlotSetChange(self):
582     """ Avoid a unnecessary call to update() when just switching current plot set! """
583     pass
584
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
597       self.repaint()
598
599   def changeFormatAxis(self) :
600     if not self.__repaintOK():
601       return
602
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')
608       else :
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')
613       else :
614         self._mplAxes.ticklabel_format(style='plain',axis='y')
615
616   def update(self):
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)
622
623     self.updateViewTitle()
624
625     # Check list of curve views:
626     set_mod = set(self._model._curves.keys())
627     set_view = set(self._curveViews.keys())
628
629     # Deleted/Added curves:
630     dels = set_view - set_mod
631     added = set_mod - set_view
632
633     for d in dels:
634       self.removeCurve(d)
635
636     if not len(self._curveViews):
637       # Reset color cycle
638       self._mplAxes.set_prop_cycle(None)
639
640     for a in added:
641       self.appendCurve(a)
642
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)
647
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()
653
654     # Redo auto-fit
655     if len(self._curveViews):
656       self.autoFit(repaint=False)
657     self.repaint()
658
659   def onDataChange(self):
660     # the rest is done in the CurveView:
661     self.autoFit(repaint=True)
662
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)
669
670   def onContextMenu(self, position):
671     pos = self._mplCanvas.mapToGlobal(QtCore.QPoint(position.x(),position.y()))
672     self._popupMenu.exec_(pos)
673
674   def onScroll(self, event):
675     # Event location (x and y)
676     xdata = event.xdata
677     ydata = event.ydata
678
679     cur_xlim = self._mplAxes.get_xlim()
680     cur_ylim = self._mplAxes.get_ylim()
681
682     base_scale = 2.
683     if event.button == 'down':
684       # deal with zoom in
685       scale_factor = 1 / base_scale
686     elif event.button == 'up':
687       # deal with zoom out
688       scale_factor = base_scale
689     else:
690       # deal with something that should never happen
691       scale_factor = 1
692
693     new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
694     new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
695
696     relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
697     rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
698
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)])
701
702     self.repaint()
703     pass
704
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)
711     else :
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()
718
719   def onMotionEvent(self, event):
720     print("OnMotionEvent ",event.button)
721     #if event.button == 3 :
722     #  event.button = None
723     #  return True
724
725   def onReleaseEvent(self, event):
726     print("OnReleaseEvent ",event.button)
727     #if event.button == 3 :
728     #  event.button = None
729     #  return False