Salome HOME
4714b7ebe53175fe9a27419b26120a49c31d3b64
[modules/gui.git] / tools / CurvePlot / src / python / views / XYView.py
1 # Copyright (C) 2016-2019  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 View import View
23 from CurveView import CurveView
24
25 from utils import Logger, trQ
26 from PlotWidget import PlotWidget
27 from PlotSettings import PlotSettings
28 from pyqtside import QtGui, QtCore
29 from pyqtside.QtCore import QObject
30 from matplotlib.figure import Figure
31 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg, NavigationToolbar2QT
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 = QtGui.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 = QtGui.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 = QtGui.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 = QtGui.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     if self.__repaintOK():
293       self._mplAxes.relim()
294       xm, xM = self._mplAxes.xaxis.get_data_interval()
295       ym, yM = self._mplAxes.yaxis.get_data_interval()
296       i = yM-ym
297       self._mplAxes.axis([xm, xM, ym-i*self.AUTOFIT_MARGIN, yM+i*self.AUTOFIT_MARGIN])
298       if repaint:
299         self.repaint()
300
301   def zoomArea(self):
302     if self._panAction.isChecked() and self._zoomAction.isChecked():
303       self._panAction.setChecked(False)
304     # Trigger underlying matplotlib action:
305     self._mplNavigationActions["Zoom"].trigger()
306
307   def pan(self):
308     if self._panAction.isChecked() and self._zoomAction.isChecked():
309       self._zoomAction.setChecked(False)
310     # Trigger underlying matplotlib action:
311     self._mplNavigationActions["Pan"].trigger()
312
313   def getMarker(self, go_next=False):
314     if go_next:
315       self._lastMarkerID = (self._lastMarkerID+1) % len(self.CURVE_MARKERS)
316     return self.CURVE_MARKERS[self._lastMarkerID]
317
318   def changeModeCurve(self, repaint=True):
319     if not self.__repaintOK():
320       return
321     action = self._curveActionGroup.checkedAction()
322     if action is self._pointsAction :
323       for crv_view in list(self._curveViews.values()):
324         crv_view.setLineStyle("None")
325     elif action is self._linesAction :
326       for crv_view in list(self._curveViews.values()):
327         crv_view.setLineStyle("-")
328     else :
329       raise NotImplementedError
330     if repaint:
331       self.repaint()
332
333   def setXLog(self, log, repaint=True):
334     if not self.__repaintOK():
335       return
336     self._blockLogSignal = True
337     if log:
338       self._mplAxes.set_xscale('log')
339       self._horLogarithmicAction.setChecked(True)
340     else:
341       self._mplAxes.set_xscale('linear')
342       self._horLinearAction.setChecked(True)
343     if repaint:
344       self.autoFit()
345       self.repaint()
346     self._blockLogSignal = False
347
348   def setYLog(self, log, repaint=True):
349     if not self.__repaintOK():
350       return
351     self._blockLogSignal = True
352     if log:
353       self._mplAxes.set_yscale('log')
354       self._verLogarithmicAction.setChecked(True)
355     else:
356       self._mplAxes.set_yscale('linear')
357       self._verLinearAction.setChecked(True)
358     if repaint:
359       self.autoFit()
360       self.repaint()
361     self._blockLogSignal = False
362
363   def setXSciNotation(self, sciNotation, repaint=True):
364     self._axisXSciNotation = sciNotation
365     self.changeFormatAxis()
366     if repaint:
367       self.repaint()
368
369   def setYSciNotation(self, sciNotation, repaint=True):
370     self._axisYSciNotation = sciNotation
371     self.changeFormatAxis()
372     if repaint:
373       self.repaint()
374
375   def onViewHorizontalMode(self, checked=True, repaint=True):
376     if self._blockLogSignal:
377       return
378     action = self._horActionGroup.checkedAction()
379     if action is self._horLinearAction:
380       self.setXLog(False, repaint)
381     elif action is self._horLogarithmicAction:
382       self.setXLog(True, repaint)
383     else:
384       raise NotImplementedError
385
386   def onViewVerticalMode(self, checked=True, repaint=True):
387     if self._blockLogSignal:
388       return
389     action = self._verActionGroup.checkedAction()
390     if action is self._verLinearAction:
391       self.setYLog(False, repaint)
392     elif action is self._verLogarithmicAction:
393       self.setYLog(True, repaint)
394     else:
395       raise NotImplementedError
396     if repaint:
397       self.repaint()
398
399   def __adjustFigureMargins(self, withLegend):
400     """ Adjust figure margins to make room for the legend """
401     if withLegend:
402       leg = self._legend
403       bbox = leg.get_window_extent()
404       # In axes coordinates:
405       bbox2 = bbox.transformed(leg.figure.transFigure.inverted())
406       if self._legendLoc == "right":
407         self._mplFigure.subplots_adjust(right=1.0-(bbox2.width+0.02))
408       elif self._legendLoc == "bottom":
409         self._mplFigure.subplots_adjust(bottom=bbox2.height+0.1)
410     else:
411       # Reset to default (rc) values
412       self._mplFigure.subplots_adjust(bottom=0.1, right=0.9)
413
414   def setLegendVisible(self, visible, repaint=True):
415     if visible and not self._actionLegend.isChecked():
416       self._actionLegend.setChecked(True)
417       self.showHideLegend(repaint=repaint)
418     if not visible and self._actionLegend.isChecked():
419       self._actionLegend.setChecked(False)
420       self.showHideLegend(repaint=repaint)
421
422   def showHideLegend(self, actionChecked=None, repaint=True):
423     if not self.__repaintOK():  # Show/hide legend is extremely costly
424       return
425
426     show = self._actionLegend.isChecked()
427     nCurves = len(self._curveViews)
428     if nCurves > 10: fontSize = 'x-small'
429     else:            fontSize = None
430
431     if nCurves == 0:
432       # Remove legend
433       leg = self._mplAxes.legend()
434       if leg is not None: leg.remove()
435     if show and nCurves > 0:
436       # Recreate legend from scratch
437       if self._legend is not None:
438         self._legend = None
439         self._mplAxes._legend = None
440       if self._legendLoc == "bottom":
441         self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(0.0, -0.05, 1.0, -0.05),
442                                             borderaxespad=0.0, mode="expand", fancybox=True,
443                                             shadow=True, ncol=3, prop={'size':fontSize, 'style': 'italic'})
444       elif self._legendLoc == "right":
445         self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(1.02,1.0), borderaxespad=0.0,
446                                             ncol=1, fancybox=True, shadow=True, prop={'size':fontSize, 'style': 'italic'})
447       else:
448         raise Exception("Invalid legend placement! Must be 'bottom' or 'right'")
449       # Canvas must be drawn so we can adjust the figure placement:
450       self._mplCanvas.draw()
451       self.__adjustFigureMargins(withLegend=True)
452     else:
453       if self._legend is None:
454         # Nothing to do
455         return
456       else:
457         self._legend.set_visible(False)
458         self._legend = None
459         self._mplAxes._legend = None
460         self._mplCanvas.draw()
461         self.__adjustFigureMargins(withLegend=False)
462
463     curr_crv = self._model._currentCurve
464     if curr_crv is None: curr_title = None
465     else:                curr_title = curr_crv.getTitle()
466     if self._legend is not None:
467       for label in self._legend.get_texts() :
468         text = label.get_text()
469         if (text == curr_title):
470           label.set_backgroundcolor('0.85')
471         else :
472           label.set_backgroundcolor('white')
473
474     if repaint:
475       self.repaint()
476
477   def onSettings(self, trigger=False, dlg_test=None):
478     dlg = dlg_test or PlotSettings()
479     dlg.titleEdit.setText(self._mplAxes.get_title())
480     dlg.axisXTitleEdit.setText(self._mplAxes.get_xlabel())
481     dlg.axisYTitleEdit.setText(self._mplAxes.get_ylabel())
482     dlg.gridCheckBox.setChecked(self._mplAxes.xaxis._gridOnMajor)  # could not find a relevant API to check this
483     dlg.axisXSciCheckBox.setChecked(self._axisXSciNotation)
484     dlg.axisYSciCheckBox.setChecked(self._axisYSciNotation)
485     xmin, xmax = self._mplAxes.get_xlim()
486     ymin, ymax = self._mplAxes.get_ylim()
487     xminText = "%g" %xmin
488     xmaxText = "%g" %xmax
489     yminText = "%g" %ymin
490     ymaxText = "%g" %ymax
491     dlg.axisXMinEdit.setText(xminText)
492     dlg.axisXMaxEdit.setText(xmaxText)
493     dlg.axisYMinEdit.setText(yminText)
494     dlg.axisYMaxEdit.setText(ymaxText)
495     # List of markers
496     dlg.markerCurve.clear()
497     for marker in self.CURVE_MARKERS :
498       dlg.markerCurve.addItem(marker)
499     curr_crv = self._model.getCurrentCurve()
500     if not curr_crv is None:
501       dlg.colorCurve.setEnabled(True)
502       dlg.markerCurve.setEnabled(True)
503       name = curr_crv.getTitle()
504       dlg.nameCurve.setText(name)
505       view = self._curveViews[curr_crv.getID()]
506       marker = view.getMarker()
507       color = view.getColor()
508       index = dlg.markerCurve.findText(marker)
509       dlg.markerCurve.setCurrentIndex(index)
510       rgb = colors.colorConverter.to_rgb(color)
511       dlg.setRGB(rgb[0],rgb[1],rgb[2])
512     else :
513       dlg.colorCurve.setEnabled(False)
514       dlg.markerCurve.setEnabled(False)
515       dlg.nameCurve.setText("")
516       view = None
517     if self._legend is None:
518       dlg.showLegendCheckBox.setChecked(False)
519       dlg.legendPositionComboBox.setEnabled(False)
520     else :
521       if self._legend.get_visible():
522         dlg.showLegendCheckBox.setChecked(True)
523         dlg.legendPositionComboBox.setEnabled(True)
524         if self._legendLoc == "bottom":
525           dlg.legendPositionComboBox.setCurrentIndex(0)
526         elif self._legendLoc == "right" :
527           dlg.legendPositionComboBox.setCurrentIndex(1)
528       else :
529         dlg.showLegendCheckBox.setChecked(False)
530         dlg.legendPositionComboBox.setEnabled(False)
531
532     if dlg.exec_():
533       # Title
534       self._model.setTitle(dlg.titleEdit.text())
535       # Axis
536       self._model.setXLabel(dlg.axisXTitleEdit.text())
537       self._model.setYLabel(dlg.axisYTitleEdit.text())
538       # Grid
539       if dlg.gridCheckBox.isChecked() :
540         self._mplAxes.grid(True)
541       else :
542         self._mplAxes.grid(False)
543       # Legend
544       if  dlg.showLegendCheckBox.isChecked():
545         self._actionLegend.setChecked(True)
546         if dlg.legendPositionComboBox.currentIndex() == 0 :
547           self._legendLoc = "bottom"
548         elif dlg.legendPositionComboBox.currentIndex() == 1 :
549           self._legendLoc = "right"
550       else :
551         self._actionLegend.setChecked(False)
552       xminText = dlg.axisXMinEdit.text()
553       xmaxText = dlg.axisXMaxEdit.text()
554       yminText = dlg.axisYMinEdit.text()
555       ymaxText = dlg.axisYMaxEdit.text()
556       self._mplAxes.axis([float(xminText), float(xmaxText), float(yminText), float(ymaxText)] )
557       self._axisXSciNotation = dlg.axisXSciCheckBox.isChecked()
558       self._axisYSciNotation = dlg.axisYSciCheckBox.isChecked()
559       self.changeFormatAxis()
560       # Color and marker of the curve
561       if view:
562         view.setColor(dlg.getRGB())
563         view.setMarker(self.CURVE_MARKERS[dlg.markerCurve.currentIndex()])
564       self.showHideLegend(repaint=True)
565       self._mplCanvas.draw()
566     pass
567
568   def updateViewTitle(self):
569     s = ""
570     if self._model._title != "":
571       s = " - %s" % self._model._title
572     title = "CurvePlot (%d)%s" % (self._model.getID(), s)
573     self._sgPyQt.setViewTitle(self._salomeViewID, title)
574
575   def onCurrentPlotSetChange(self):
576     """ Avoid a unnecessary call to update() when just switching current plot set! """
577     pass
578
579   def onCurrentCurveChange(self):
580     curr_crv2 = self._model.getCurrentCurve()
581     if curr_crv2 != self._currCrv:
582       if self._currCrv is not None:
583         view = self._curveViews[self._currCrv.getID()]
584         view.toggleHighlight(False)
585       if not curr_crv2 is None:
586         view = self._curveViews[curr_crv2.getID()]
587         view.toggleHighlight(True)
588       self._currCrv = curr_crv2
589       self.showHideLegend(repaint=False) # redo legend
590       self.repaint()
591
592   def changeFormatAxis(self) :
593     if not self.__repaintOK():
594       return
595
596     # don't try to switch to sci notation if we are not using the
597     # matplotlib.ticker.ScalarFormatter (i.e. if in Log for ex.)
598     if self._horLinearAction.isChecked():
599       if self._axisXSciNotation :
600         self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='x')
601       else :
602         self._mplAxes.ticklabel_format(style='plain',axis='x')
603     if self._verLinearAction.isChecked():
604       if self._axisYSciNotation :
605         self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='y')
606       else :
607         self._mplAxes.ticklabel_format(style='plain',axis='y')
608
609   def update(self):
610     if self._salomeViewID is None:
611       self.createPlotWidget()
612       self._salomeViewID = self._sgPyQt.createView("CurvePlot", self._plotWidget)
613       Logger.Debug("Creating SALOME view ID=%d" % self._salomeViewID)
614       self._sgPyQt.setViewVisible(self._salomeViewID, True)
615
616     self.updateViewTitle()
617
618     # Check list of curve views:
619     set_mod = set(self._model._curves.keys())
620     set_view = set(self._curveViews.keys())
621
622     # Deleted/Added curves:
623     dels = set_view - set_mod
624     added = set_mod - set_view
625
626     for d in dels:
627       self.removeCurve(d)
628
629     if not len(self._curveViews):
630       # Reset color cycle
631       self._mplAxes.set_color_cycle(None)
632
633     for a in added:
634       self.appendCurve(a)
635
636     # Axes labels and title
637     self._mplAxes.set_xlabel(self._model._xlabel)
638     self._mplAxes.set_ylabel(self._model._ylabel)
639     self._mplAxes.set_title(self._model._title)
640
641     self.onViewHorizontalMode(repaint=False)
642     self.onViewVerticalMode(repaint=False)
643     self.changeModeCurve(repaint=False)
644     self.showHideLegend(repaint=False)   # The canvas is repainted anyway (needed to get legend bounding box)
645     self.changeFormatAxis()
646
647     # Redo auto-fit
648     self.autoFit(repaint=False)
649     self.repaint()
650
651   def onDataChange(self):
652     # the rest is done in the CurveView:
653     self.autoFit(repaint=True)
654
655   def onMousePress(self, event):
656     if event.button == 3 :
657       if self._panAction.isChecked():
658         self._panAction.setChecked(False)
659       if self._zoomAction.isChecked():
660         self._zoomAction.setChecked(False)
661
662   def onContextMenu(self, position):
663     pos = self._mplCanvas.mapToGlobal(QtCore.QPoint(position.x(),position.y()))
664     self._popupMenu.exec_(pos)
665
666   def onScroll(self, event):
667     # Event location (x and y)
668     xdata = event.xdata
669     ydata = event.ydata
670
671     cur_xlim = self._mplAxes.get_xlim()
672     cur_ylim = self._mplAxes.get_ylim()
673
674     base_scale = 2.
675     if event.button == 'down':
676       # deal with zoom in
677       scale_factor = 1 / base_scale
678     elif event.button == 'up':
679       # deal with zoom out
680       scale_factor = base_scale
681     else:
682       # deal with something that should never happen
683       scale_factor = 1
684
685     new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
686     new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
687
688     relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
689     rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
690
691     self._mplAxes.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
692     self._mplAxes.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
693
694     self.repaint()
695     pass
696
697   def onPressEvent(self, event):
698     if event.button == 3 :
699       #self._mplCanvas.emit(QtCore.SIGNAL("button_release_event()"))
700       canvasSize = event.canvas.geometry()
701       point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
702       self._popupMenu.exec_(point)
703     else :
704       print("Press event on the other button")
705     #if event.button == 3 :
706     #  canvasSize = event.canvas.geometry()
707     #  point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
708     #  self._popupMenu.move(point)
709     #  self._popupMenu.show()
710
711   def onMotionEvent(self, event):
712     print("OnMotionEvent ",event.button)
713     #if event.button == 3 :
714     #  event.button = None
715     #  return True
716
717   def onReleaseEvent(self, event):
718     print("OnReleaseEvent ",event.button)
719     #if event.button == 3 :
720     #  event.button = None
721     #  return False