]> SALOME platform Git repositories - modules/gui.git/blob - tools/CurvePlot/src/python/controller/PlotController.py
Salome HOME
Merge remote-tracking branch 'origin/V8_3_BR' into ngr/python3_dev
[modules/gui.git] / tools / CurvePlot / src / python / controller / PlotController.py
1 from CurveBrowserView import CurveBrowserView
2 from PlotManager import PlotManager
3 from CurveTabsView import CurveTabsView
4 from CurveModel import CurveModel
5 from TableModel import TableModel
6 from utils import Logger
7 import numpy as np
8
9 class PlotController(object):
10   """ Controller for 2D curve plotting functionalities.
11   """
12   __UNIQUE_INSTANCE = None  # my poor impl. of a singleton
13   
14   ## For testing purposes:
15   WITH_CURVE_BROWSER = True
16   WITH_CURVE_TABS = True
17   
18   def __init__(self, sgPyQt=None):
19     if self.__UNIQUE_INSTANCE is None:
20       self.__trueInit(sgPyQt)
21     else:
22       raise Exception("The PlotController must be a singleton - use GetInstance()")
23
24   def __trueInit(self, sgPyQt=None):
25     if sgPyQt is None:
26       import SalomePyQt
27       sgPyQt = SalomePyQt.SalomePyQt()
28     self._sgPyQt = sgPyQt
29     self._modelViews = {}
30     self._browserContextualMenu = None
31     self._blockNotifications = False
32     self._blockViewClosing = False
33     self._callbacks = []
34     
35     self._plotManager = PlotManager(self)
36     
37     if self.WITH_CURVE_BROWSER:
38       self._curveBrowserView = CurveBrowserView(self)
39       self.associate(self._plotManager, self._curveBrowserView)
40     else:
41       self._curveBrowserView = None  
42     if self.WITH_CURVE_TABS:
43       self._curveTabsView = CurveTabsView(self)
44       self.associate(self._plotManager, self._curveTabsView)
45     else:
46       self._curveTabsView = None
47     PlotController.__UNIQUE_INSTANCE = self
48   
49   @classmethod
50   def GetInstance(cls, sgPyQt=None):
51     if cls.__UNIQUE_INSTANCE is None:
52       # First instanciation:
53       PlotController(sgPyQt)
54     return cls.__UNIQUE_INSTANCE
55   
56   @classmethod
57   def Destroy(cls):
58     cls.__UNIQUE_INSTANCE = None
59   
60   def setFixedSizeWidget(self):
61     """ For testing purposes - ensure visible Qt widgets have a fixed size.
62     """
63     if self.WITH_CURVE_BROWSER:
64       self._curveBrowserView.treeWidget.resize(100,200)
65     if self.WITH_CURVE_TABS:
66       self._sgPyQt._tabWidget.resize(600,600)
67   
68   def associate(self, model, view):
69     """
70     Associates a model to a view, and sets the view to listen to this model 
71     changes.
72     
73     :param model: Model -- The model to be associated to the view.
74     :param view: View -- The view.
75     
76     """    
77     if model is None or view is None:
78         return
79   
80     view.setModel(model)
81     self.setModelListener(model, view)
82   
83   def setModelListener(self, model, view):
84     """
85     Sets a view to listen to all changes of the given model
86     """
87     l = self._modelViews.setdefault(model, [])
88     if not view in l and view is not None:
89       l.append(view) 
90   
91   def removeModelListeners(self, model):
92     """ 
93     Removes the given model from the list of listeners. All views previously connected to this model
94     won't receive its update notification anymore.
95     """
96     self._modelViews.pop(model)
97   
98   def notify(self, model, what=""):
99     """
100     Notifies the view when model changes.
101     
102     :param model: Model -- The updated model.
103     """
104     if model is None or self._blockNotifications:
105       return
106     
107     if model not in self._modelViews:
108       return
109     
110     for view in self._modelViews[model]:
111       method = "on%s" % what
112       if what != "" and what is not None and hasattr(view, method):
113         exec("view.%s()" % method)
114       elif hasattr(view, "update"):
115         # Generic update:
116         view.update()
117     
118   def setBrowserContextualMenu(self, menu):
119     """ Provide a menu to be contextually shown in the curve browser """
120     self._browserContextualMenu = menu
121     
122   def setCurvePlotRequestingClose(self, bool):
123     self._blockViewClosing = bool
124     
125   def onCurrentCurveChange(self):
126     ps = self._plotManager.getCurrentPlotSet()
127     if not ps is None:
128       crv = ps.getCurrentCurve()
129       if crv is not None:
130         crv_id = crv.getID() 
131         for c in self._callbacks:
132           c(crv_id)
133     
134   #####
135   ##### Public static API
136   #####
137   
138   @classmethod
139   def AddCurve(cls, x, y, curve_label="", x_label="", y_label="", append=True):
140     """ Add a new curve and make the plot set where it is drawn the active one.
141         If no plot set exists, or none is active, a new plot set will be created, even if append is True.
142         @param x x data
143         @param y y data
144         @param curve_label label of the curve being ploted (optional, default to empty string). This is what is
145         shown in the legend.
146         @param x_label label for the X axis
147         @param y_label label for the Y axis
148         @param append whether to add the curve to the active plot set (default) or into a new one.
149         @return the id of the created curve, and the id of the corresponding plot set.
150     """
151     from XYView import XYView
152     control = cls.GetInstance()
153     pm = control._plotManager
154     t = TableModel(control)
155     data = np.transpose(np.vstack([x, y]))
156     t.setData(data)
157     # ensure a single Matplotlib repaint for all operations to come in AddCurve
158     prevLock = pm.isRepaintLocked()
159     if not prevLock:
160       pm.lockRepaint()
161     curveID, plotSetID = control.plotCurveFromTable(t, x_col_index=0, y_col_index=1, 
162                                                     curve_label=curve_label, append=append)
163     ps = pm._plotSets[plotSetID]
164     if x_label != "":
165       ps.setXLabel(x_label)
166     if y_label != "": 
167       ps.setYLabel(y_label)
168     if not prevLock:
169       pm.unlockRepaint()
170     return curveID, plotSetID
171
172   @classmethod  
173   def ExtendCurve(cls, crv_id, x, y):
174     """ Add new points to an already created curve
175     @raise if invalid plot set ID is given
176     """
177     control = cls.GetInstance()
178     ps = control._plotManager.getPlotSetContainingCurve(crv_id)
179     if ps is None:
180       raise ValueError("Curve ID (%d) not found for extension!" % crv_id)
181     crv_mod = ps._curves[crv_id]
182     data = np.transpose(np.vstack([x, y]))
183     crv_mod.extendData(data)
184     
185   @classmethod
186   def ResetCurve(cls, crv_id):
187     """ Reset a given curve: all data are cleared, but the curve is still 
188     alive with all its attributes (color, etc ...). Mostly used in conjunction
189     with ExtendCurve above
190     @raise if invalid plot set ID is given
191     """
192     control = cls.GetInstance()
193     ps = control._plotManager.getPlotSetContainingCurve(crv_id)
194     if ps is None:
195       raise ValueError("Curve ID (%d) not found for reset!" % crv_id)
196     crv_mod = ps._curves[crv_id]
197     crv_mod.resetData()
198     
199   @classmethod
200   def AddPlotSet(cls, title=""):
201     """ Creates a new plot set (a tab with several curves) and returns its ID. A title can be passed,
202     otherwise a default one will be created.
203     By default this new plot set becomes the active one.
204     """
205     control = cls.GetInstance()
206     ps = control._plotManager.createXYPlotSet()
207     control.setModelListener(ps, control._curveBrowserView)
208     # Controller itself must be notified for curve picking:
209     control.setModelListener(ps, control)
210     if title != "":
211       ps.setTitle(title)
212     return ps.getID()
213             
214   @classmethod
215   def CopyCurve(cls, curve_id, plot_set_id):
216     """ Copy a given curve to a given plot set ID
217     @return ID of the newly created curve
218     """
219     control = cls.GetInstance()
220     psID = cls.GetPlotSetID(curve_id)
221     if psID == -1:
222       raise ValueError("Curve ID (%d) not found for duplication!" % curve_id)
223     plot_set_src = control._plotManager._plotSets[psID]
224     plot_set_tgt = control._plotManager._plotSets.get(plot_set_id, None)
225     if plot_set_tgt is None:
226       raise ValueError("Plot set ID (%d) invalid for duplication!" % plot_set_id)
227     crv = plot_set_src._curves[curve_id]
228     new_crv = crv.clone()
229     control.setModelListener(new_crv, control._curveBrowserView)
230     plot_set_tgt.addCurve(new_crv)
231     return new_crv.getID()
232       
233   @classmethod
234   def DeleteCurve(cls, curve_id=-1):
235     """ By default, delete the current curve, if any. Otherwise do nothing.
236         @return the id of the deleted curve or -1
237     """
238     Logger.Debug("Delete curve")
239     control = cls.GetInstance()
240     # Find the right plot set:
241     if curve_id == -1:
242       curve_id = cls.GetCurrentCurveID()
243       if curve_id == -1:
244         # No current curve, do nothing
245         return -1 
246       
247     psID = cls.GetPlotSetID(curve_id)
248     if psID == -1:
249       raise ValueError("Curve ID (%d) not found for deletion!" % curve_id)
250     crv = control._plotManager._plotSets[psID]._curves[curve_id]
251     control._plotManager._plotSets[psID].removeCurve(curve_id)
252     control.removeModelListeners(crv)
253     return curve_id
254   
255   @classmethod
256   def DeletePlotSet(cls, plot_set_id=-1):
257     """ By default, delete the current plot set, if any. Otherwise do nothing.
258         This will automatically make the last added plot set the current one.
259         @return the id of the deleted plot set or -1
260     """
261     Logger.Debug("PlotController::DeletePlotSet %d" % plot_set_id)
262     control = cls.GetInstance()
263     # Find the right plot set:
264     if plot_set_id == -1:
265       plot_set_id = cls.GetCurrentPlotSetID()
266       if plot_set_id == -1:
267         # No current, do nothing
268         return -1
269
270     ps = control._plotManager.removeXYPlotSet(plot_set_id)
271     for _, crv in list(ps._curves.items()):
272       control.removeModelListeners(crv)
273     control.removeModelListeners(ps)
274     psets = control._plotManager._plotSets 
275     if len(psets):
276       control._plotManager.setCurrentPlotSet(list(psets.keys())[-1])
277     return plot_set_id
278   
279   @classmethod
280   def usedMem(cls):
281       import gc
282       gc.collect()
283       import resource
284       m = resource.getrusage(resource.RUSAGE_SELF)[2]*resource.getpagesize()/1e6
285       print("** Used memory: %.2f Mb" % m)
286   
287   @classmethod
288   def DeleteCurrentItem(cls):
289     """ Delete currently active item, be it a plot set or a curve.
290     @return (True, plot_sed_id) if a plot set was deleted or (False, curve_id) if a curve was deleted, or (True, -1)
291     if nothing was deleted.
292     """
293     c_id = cls.GetCurrentCurveID()
294     ps_id = cls.GetCurrentPlotSetID()
295     ret = True, -1
296     if ps_id == -1:
297       Logger.Info("PlotController.DeleteCurrentItem(): nothing selected, nothing to delete!")
298       return True,-1
299     # Do we delete a curve or a full plot set    
300     if c_id == -1:
301       cls.DeletePlotSet(ps_id)
302       ret = True, ps_id
303     else:
304       cls.DeleteCurve(c_id)
305       ret = False, c_id
306     return ret
307   
308   @classmethod
309   def ClearPlotSet(cls, ps_id=-1):
310     """ Clear all curves in a given plot set. By default clear the current plot set without deleting it,
311     if no default plot set is currently active, do nothing.
312     @return id of the cleared plot set
313     @raise if invalid plot set ID is given
314     """
315     pm = cls.GetInstance()._plotManager
316     if ps_id == -1:
317       ps_id = cls.GetCurrentPlotSetID()
318       if ps_id == -1:
319         return ps_id
320     ps = pm._plotSets.get(ps_id, None)
321     if ps is None:
322       raise ValueError("Invalid plot set ID (%d)!" % ps_id)
323     ps.eraseAll()
324     return ps_id
325   
326 #   @classmethod
327 #   def ClearAll(cls):
328 #     # TODO: optimize
329 #     pm = cls.GetInstance()._plotManager
330 #     ids = pm._plotSets.keys()
331 #     for i in ids:
332 #       cls.DeletePlotSet(i)
333   
334   @classmethod
335   def SetXLabel(cls, x_label, plot_set_id=-1):
336     """  By default set the X axis label for the current plot set, if any. Otherwise do nothing.
337          @return True if the label was set
338     """
339     pm = cls.GetInstance()._plotManager
340     if plot_set_id == -1:
341       plot_set_id = cls.GetCurrentPlotSetID()
342       if plot_set_id == -1:
343         # Do nothing
344         return False 
345     ps = pm._plotSets.get(plot_set_id, None)
346     if ps is None:
347       raise Exception("Invalid plot set ID (%d)!" % plot_set_id)
348     ps.setXLabel(x_label)
349     return True
350      
351   @classmethod 
352   def SetYLabel(cls, y_label, plot_set_id=-1):
353     """ By default set the Y axis label for the current plot set, if any. Otherwise do nothing.
354          @return True if the label was set
355     """
356     pm = cls.GetInstance()._plotManager
357     if plot_set_id == -1:
358       plot_set_id = cls.GetCurrentPlotSetID()
359       if plot_set_id == -1:
360         # Do nothing
361         return False 
362     ps = pm._plotSets.get(plot_set_id, None)
363     if ps is None:
364       raise Exception("Invalid plot set ID (%d)!" % plot_set_id)
365     ps.setYLabel(y_label)
366     return True
367      
368   @classmethod 
369   def SetPlotSetTitle(cls, title, plot_set_id=-1):
370     """ By default set the title for the current plot set, if any. Otherwise do nothing.
371          @return True if the title was set
372     """
373     pm = cls.GetInstance()._plotManager
374     if plot_set_id == -1:
375       plot_set_id = cls.GetCurrentPlotSetID()
376       if plot_set_id == -1:
377         # Do nothing
378         return False 
379     ps = pm._plotSets.get(plot_set_id, None)
380     if ps is None:
381       raise Exception("Invalid plot set ID (%d)!" % plot_set_id)
382     ps.setTitle(title)
383     return True
384   
385   @classmethod
386   def GetPlotSetID(cls, curve_id):
387     """ @return plot set id for a given curve or -1 if invalid curve ID
388     """
389     control = cls.GetInstance()
390     cps = control._plotManager.getPlotSetContainingCurve(curve_id)
391     if cps is None:
392       return -1
393     return cps.getID()
394   
395   @classmethod
396   def GetPlotSetIDByName(cls, name):
397     """ @return the first plot set whose name matches the provided name. Otherwise returns -1
398     """ 
399     pm = cls.GetInstance()._plotManager
400     for _, ps in list(pm._plotSets.items()):
401       if ps._title == name:
402         return ps.getID()
403     return -1
404   
405   @classmethod
406   def GetAllPlotSets(cls):
407     """ @return two lists: plot set names, and corresponding plot set IDs
408     """
409     pm = cls.GetInstance()._plotManager
410     it = list(pm._plotSets.items())
411     ids, inst, titles = [], [], []
412     if len(it):  
413       ids, inst = list(zip(*it))        
414     if len(inst):
415       titles = [i.getTitle() for i in inst]
416     return list(ids), titles
417   
418   @classmethod
419   def GetCurrentCurveID(cls):
420     """ @return current curve ID or -1 if no curve is currently active
421     """
422     control = cls.GetInstance()
423     crv = control._plotManager.getCurrentCurve()
424     if crv is None:
425       return -1
426     return crv.getID()
427      
428   @classmethod   
429   def GetCurrentPlotSetID(cls):
430     """ @return current plot set ID or -1 if no plot set is currently active
431     """
432     control = cls.GetInstance()
433     cps = control._plotManager.getCurrentPlotSet()
434     if cps is None:
435       return -1
436     return cps.getID()  
437
438   @classmethod
439   def SetCurrentPlotSet(cls, ps_id):
440     """ Set the current active plot set. Use -1 to unset any current plot set.
441     @throw if invalid ps_id
442     """
443     control = cls.GetInstance()
444     control._plotManager.setCurrentPlotSet(ps_id)
445
446   @classmethod
447   def SetCurrentCurve(cls, crv_id):
448     """ Set the current active curve.
449     @return corresponding plot set ID
450     @throw if invalid crv_id
451     """
452     control = cls.GetInstance()
453     ps_id = control._plotManager.setCurrentCurve(crv_id)
454     return ps_id
455
456   @classmethod
457   def ActiveViewChanged(cls, viewID):
458     """ This method is to be plugged direclty in the activeViewChanged() slot of a standard
459     Python SALOME module so that the curve browser stays in sync with the selected SALOME view
460     """
461     control = cls.GetInstance()
462     # Get XYView from SALOME view ID
463     xyview = control._curveTabsView._XYViews.get(viewID, None)
464     if not xyview is None:
465       plotSetID = xyview.getModel().getID()
466       control._plotManager.setCurrentPlotSet(plotSetID)
467
468   @classmethod
469   def ToggleCurveBrowser(cls, active):
470     if cls.__UNIQUE_INSTANCE is not None:
471       raise Exception("ToggleCurveBrowser() must be invoked before doing anything in plot2D!")
472     cls.WITH_CURVE_BROWSER = active
473     
474   @classmethod
475   def IsValidPlotSetID(cls, plot_set_id):
476     """ 
477     @return True if plot_set_id is the identifier of a valid and existing plot set.
478     """
479     control = cls.GetInstance()
480     return plot_set_id in control._plotManager._plotSets
481
482   @classmethod
483   def GetSalomeViewID(cls, plot_set_id):
484     """
485     @return the salome view ID associated to a given plot set. -1 if invalid plot_set_id
486     """
487     control = cls.GetInstance()
488     d = control._curveTabsView.mapModId2ViewId()
489     return d.get(plot_set_id, -1)
490
491   @classmethod
492   def OnSalomeViewTryClose(cls, salome_view_id):
493     control = cls.GetInstance()
494     if not control._blockViewClosing:
495       Logger.Debug("PlotController::OnSalomeViewTryClose %d" % salome_view_id)
496 #       control._sgPyQt.setViewClosable(salome_view_id, False)
497       # Get XYView from SALOME view ID
498       xyview = control._curveTabsView._XYViews.get(salome_view_id, None)
499       if not xyview is None:
500         plotSetID = xyview.getModel().getID()
501         Logger.Debug("PlotController::OnSalomeViewTryClose internal CurvePlot view ID is %d" % plotSetID)
502         control._plotManager.removeXYPlotSet(plotSetID)
503       else:
504         Logger.Warning("Internal error - could not match SALOME view ID %d with CurvePlot view!" % salome_view_id)
505
506   @classmethod
507   def SetCurveMarker(cls, crv_id, marker):
508     """ Change curve marker. Available markers are:
509     CURVE_MARKERS = [ "o" ,#  circle
510                     "*",  # star
511                     "+",  # plus
512                     "x",  # x
513                     "s",  # square
514                     "p",  # pentagon
515                     "h",  # hexagon1
516                     "8",  # octagon
517                     "D",  # diamond
518                     "^",  # triangle_up
519                     "<",  # triangle_left
520                     ">",  # triangle_right
521                     "1",  # tri_down
522                     "2",  # tri_up
523                     "3",  # tri_left
524                     "4",  # tri_right
525                     "v",  # triangle_down
526                     "H",  # hexagon2
527                     "d",  # thin diamond
528                     "",   # NO MARKER
529                    ]
530     @raise if invalid curve ID or marker
531     """
532     from XYView import XYView
533     from CurveView import CurveView
534     if not marker in XYView.CURVE_MARKERS:
535       raise ValueError("Invalid marker: '%s'" % marker)
536     
537     cont = cls.GetInstance()
538     for mod, views in list(cont._modelViews.items()):
539       if isinstance(mod, CurveModel) and mod.getID() == crv_id:
540         for v in views:
541           if isinstance(v, CurveView):
542             v.setMarker(marker)
543             # Update curve display and legend:
544             v._parentXYView.repaint()
545             v._parentXYView.showHideLegend()
546             found = True
547         
548     if not found:
549       raise Exception("Invalid curve ID or curve currently not displayed (curve_id=%d)!" % crv_id)
550
551   @classmethod
552   def SetCurveLabel(cls, crv_id, label):
553     """ Change curve label
554     @raise if invalid curve id
555     """
556     cont = cls.GetInstance()
557     cps = cont._plotManager.getPlotSetContainingCurve(crv_id)
558     if cps is None:
559       raise ValueError("Invalid curve ID: %d" % crv_id)
560     cps._curves[crv_id].setTitle(label)
561
562   @classmethod
563   def __XYViewOperation(cls, func, ps_id, args, kwargs):
564     """ Private. To factorize methods accessing the XYView to change a display element. """
565     from XYPlotSetModel import XYPlotSetModel
566     from XYView import XYView
567     
568     cont = cls.GetInstance()
569     for mod, views in list(cont._modelViews.items()):
570       if isinstance(mod, XYPlotSetModel) and mod.getID() == ps_id:
571         for v in views:
572           if isinstance(v, XYView):
573             exec("v.%s(*args, **kwargs)" % func)
574             found = True
575     if not found:
576       raise Exception("Invalid plot set ID or plot set currently not displayed (ps_id=%d)!" % ps_id)
577
578
579   @classmethod
580   def SetXLog(cls, ps_id, log=True):
581     """ Toggle the X axis into logarithmic scale.
582     @param ps_id plot set ID
583     @param log if set to True, log scale is used, otherwise linear scale is used
584     @raise if invalid plot set ID
585     """
586     args, kwargs = [log], {}
587     cls.__XYViewOperation("setXLog", ps_id, args, kwargs)
588
589   @classmethod
590   def SetYLog(cls, ps_id, log=True):
591     """ Toggle the Y axis into logarithmic scale.
592     @param ps_id plot set ID
593     @param log if set to True, log scale is used, otherwise linear scale is used
594     @raise if invalid plot set ID
595     """
596     args, kwargs = [log], {}
597     cls.__XYViewOperation("setYLog", ps_id, args, kwargs)
598      
599   @classmethod
600   def SetXSciNotation(cls, ps_id, sciNotation=False):
601     """ Change the format (scientific notation or not) of the X axis.
602     @param ps_id plot set ID
603     @param sciNotation if set to True, scientific notation is used, otherwise plain notation is used
604     @raise if invalid plot set ID
605     """
606     args, kwargs = [sciNotation], {}
607     cls.__XYViewOperation("setXSciNotation", ps_id, args, kwargs)
608    
609   @classmethod
610   def SetYSciNotation(cls, ps_id, sciNotation=False):
611     """ Change the format (scientific notation or not) of the Y axis.
612     @param ps_id plot set ID
613     @param sciNotation if set to True, scientific notation is used, otherwise plain notation is used
614     @raise if invalid plot set ID
615     """
616     args, kwargs = [sciNotation], {}
617     cls.__XYViewOperation("setYSciNotation", ps_id, args, kwargs)
618
619   @classmethod
620   def SetLegendVisible(cls, ps_id, visible=True):
621     """ Change the visibility of the legend.
622     @param ps_id plot set ID
623     @param visible if set to True, show legend, otherwise hide it.
624     @raise if invalid plot set ID
625     """
626     args, kwargs = [visible], {}
627     cls.__XYViewOperation("setLegendVisible", ps_id, args, kwargs)
628     
629
630   ###
631   ### More advanced functions
632   ###
633   @classmethod
634   def RegisterCallback(cls, callback):
635     cont = cls.GetInstance()
636     cont._callbacks.append(callback)
637   
638   @classmethod
639   def ClearCallbacks(cls):
640     cont = cls.GetInstance()
641     cont._callbacks = []
642   
643   @classmethod
644   def LockRepaint(cls):
645     control = cls.GetInstance()
646     control._plotManager.lockRepaint()
647   
648   @classmethod
649   def UnlockRepaint(cls):
650     control = cls.GetInstance()
651     control._plotManager.unlockRepaint()  
652   
653   def createTable(self, data, table_name="table"):
654     t = TableModel(self)
655     t.setData(data)
656     t.setTitle(table_name)
657     return t
658      
659   def plotCurveFromTable(self, table, x_col_index=0, y_col_index=1, curve_label="", append=True):
660     """
661     :returns: a tuple containing the unique curve ID and the plot set ID 
662     """
663     # Regardless of 'append', we must create a view if none there:
664     if self._plotManager.getCurrentPlotSet() is None or not append:
665       ps = self._plotManager.createXYPlotSet()
666       self.setModelListener(ps, self._curveBrowserView)
667       # For curve picking, controller must listen:
668       self.setModelListener(ps, self)
669       cps_title = table.getTitle()
670     else:
671       cps_title = None
672
673     cps = self._plotManager.getCurrentPlotSet()
674     
675     cm = CurveModel(self, table, y_col_index)
676     cm.setXAxisIndex(x_col_index)
677     
678     # X axis label
679     tix = table.getColumnTitle(x_col_index)
680     if tix != "":
681       cps.setXLabel(tix)
682     
683     # Curve label
684     if curve_label != "":
685       cm.setTitle(curve_label)
686     else:
687       ti = table.getColumnTitle(y_col_index)
688       if ti != "":
689         cm.setTitle(ti)
690
691     # Plot set title
692     if cps_title != "" and cps_title is not None:
693       Logger.Debug("about to set title to: " + cps_title)  
694       cps.setTitle(cps_title)
695     
696     cps.addCurve(cm)
697     mp = self._curveTabsView.mapModId2ViewId()
698     xyview_id = mp[cps.getID()]
699     xyview = self._curveTabsView._XYViews[xyview_id]
700     
701     if cps_title is None:  # no plot set was created above
702       self._plotManager.setCurrentPlotSet(cps.getID())
703       
704     # Make CurveBrowser and CurveView depend on changes in the curve itself:
705     self.setModelListener(cm, self._curveBrowserView)
706     self.setModelListener(cm, xyview._curveViews[cm.getID()])
707     # Upon change on the curve also update the full plot, notably for the auto-fit and the legend:
708     self.setModelListener(cm, xyview)
709         
710     return cm.getID(),cps.getID()