source: sasview/src/sas/qtgui/Utilities/PlotView.py @ 6c7ebb88

Last change on this file since 6c7ebb88 was 8748751, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Fit result viewer SASVIEW-274, SASVIEW-275

  • Property mode set to 100644
File size: 8.0 KB
Line 
1from __future__ import with_statement, print_function
2
3# The Figure object is used to create backend-independent plot representations.
4from matplotlib.figure import Figure
5GUI_TOOLKIT = "qt5"
6from matplotlib.backends.qt_compat import QtCore, QtWidgets
7
8class EmbeddedPylab(object):
9    """
10    Define a 'with' context manager that lets you use pylab commands to
11    plot on an embedded canvas.  This is useful for wrapping existing
12    scripts in a GUI, and benefits from being more familiar than the
13    underlying object oriented interface.
14
15    As a convenience, the pylab module is returned on entry.
16
17    Example
18    -------
19
20    The following example shows how to use the WxAgg backend in a wx panel::
21
22        from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
23        from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as Toolbar
24        from matplotlib.figure import Figure
25
26        class PlotPanel(wx.Panel):
27            def __init__(self, *args, **kw):
28                wx.Panel.__init__(self, *args, **kw)
29
30                figure = Figure(figsize=(1,1), dpi=72)
31                canvas = FigureCanvas(self, wx.ID_ANY, figure)
32                self.pylab_interface = EmbeddedPylab(canvas)
33
34                # Instantiate the matplotlib navigation toolbar and explicitly show it.
35                mpl_toolbar = Toolbar(canvas)
36                mpl_toolbar.Realize()
37
38                # Create a vertical box sizer to manage the widgets in the main panel.
39                sizer = wx.BoxSizer(wx.VERTICAL)
40                sizer.Add(canvas, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, border=0)
41                sizer.Add(mpl_toolbar, 0, wx.EXPAND|wx.ALL, border=0)
42
43                # Associate the sizer with its container.
44                self.SetSizer(sizer)
45                sizer.Fit(self)
46
47            def plot(self, *args, **kw):
48                with self.pylab_interface as pylab:
49                    pylab.clf()
50                    pylab.plot(*args, **kw)
51
52    Similar patterns should work for the other backends.  Check the source code
53    in matplotlib.backend_bases.* for examples showing how to use matplotlib
54    with other GUI toolkits.
55    """
56    def __init__(self, canvas):
57        # delay loading pylab until matplotlib.use() is called
58        from matplotlib.backend_bases import FigureManagerBase
59        self.fm = FigureManagerBase(canvas, -1)
60    def __enter__(self):
61        # delay loading pylab until matplotlib.use() is called
62        import pylab
63        from matplotlib._pylab_helpers import Gcf
64        Gcf.set_active(self.fm)
65        return pylab
66    def __exit__(self, *args, **kw):
67        # delay loading pylab until matplotlib.use() is called
68        from matplotlib._pylab_helpers import Gcf
69        Gcf._activeQue = [f for f in Gcf._activeQue if f is not self.fm]
70        try:
71            del Gcf.figs[-1]
72        except KeyError:
73            pass
74
75
76class _PlotViewShared(object):
77    title = 'Plot'
78    default_size = (600, 400)
79    pylab_interface = None  # type: EmbeddedPylab
80    plot_state = None
81    model = None
82    _calculating = False
83    _need_plot =  False
84    _need_newmodel = False
85
86    def set_model(self, model):
87        self.model = model
88        if not self._is_shown():
89            self._need_newmodel = True
90        else:
91            self._redraw(newmodel=True)
92
93    def update_model(self, model):
94        #print "profile update model"
95        if self.model != model:  # ignore updates to different models
96            return
97
98        if not self._is_shown():
99            self._need_newmodel = True
100        else:
101            self._redraw(newmodel=True)
102
103    def update_parameters(self, model):
104        #print "profile update parameters"
105        if self.model != model:
106            return
107
108        if not self._is_shown():
109            self._need_plot = True
110        else:
111            self._redraw(newmodel=self._need_newmodel)
112
113    def _show(self):
114        #print "showing theory"
115        if self._need_newmodel:
116            self._redraw(newmodel=True)
117        elif self._need_plot:
118            self._redraw(newmodel=False)
119
120    def _redraw(self, newmodel=False):
121        self._need_newmodel = newmodel
122        if self._calculating:
123            # That means that I've entered the thread through a
124            # wx.Yield for the currently executing redraw.  I need
125            # to cancel the running thread and force it to start
126            # the calculation over.
127            self.cancel_calculation = True
128            #print "canceling calculation"
129            return
130
131        with self.pylab_interface as pylab:
132            self._calculating = True
133
134            #print "calling again"
135            while True:
136                #print "restarting"
137                # We are restarting the calculation, so clear the reset flag
138                self.cancel_calculation = False
139
140                if self._need_newmodel:
141                    self.newmodel()
142                    if self.cancel_calculation:
143                        continue
144                    self._need_newmodel = False
145                self.plot()
146                if self.cancel_calculation:
147                    continue
148                pylab.draw()
149                break
150        self._need_plot = False
151        self._calculating = False
152
153    def get_state(self):
154        #print "returning state",self.model,self.plot_state
155        return self.model, self.plot_state
156
157    def set_state(self, state):
158        self.model, self.plot_state = state
159        #print "setting state",self.model,self.plot_state
160        self.plot()
161
162    def menu(self):
163        """
164        Return a model specific menu
165        """
166        return None
167
168    def newmodel(self, model=None):
169        """
170        New or updated model structure.  Do any sort or precalculation you
171        need.  plot will be called separately when you are done.
172
173        For long calculations, periodically perform wx.YieldIfNeeded()
174        and then if self.cancel_calculation is True, return from the plot.
175        """
176        pass
177
178    def plot(self):
179        """
180        Plot to the current figure.  If model has a plot method,
181        just use that.
182
183        For long calculations, periodically perform wx.YieldIfNeeded()
184        and then if self.cancel_calculation is True, return from the plot.
185        """
186        if hasattr(self.model, 'plot'):
187            self.model.plot()
188        else:
189            raise NotImplementedError("PlotPanel needs a plot method")
190
191class PlotView(QtWidgets.QWidget, _PlotViewShared):
192    def __init__(self, *args, **kw):
193        QtWidgets.QWidget.__init__(self, *args, **kw)
194        #import matplotlib.backends.backend_qt4agg
195        #matplotlib.backends.backend_qt4agg.DEBUG = True
196        from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
197        from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as Toolbar
198
199        #QtWidgets.QWidget.__init__(self, *args, **kw)
200
201        # Can specify name on
202        if 'title' in kw:
203            self.title = kw['title']
204
205        # Instantiate a figure object that will contain our plots.
206        figure = Figure(figsize=(1,1), dpi=72)
207
208        # Initialize the figure canvas, mapping the figure object to the plot
209        # engine backend.
210        canvas = FigureCanvas(figure)
211
212        # Wx-Pylab magic ...
213        # Make our canvas an active figure manager for pylab so that when
214        # pylab plotting statements are executed they will operate on our
215        # canvas and not create a new frame and canvas for display purposes.
216        # This technique allows this application to execute code that uses
217        # pylab stataments to generate plots and embed these plots in our
218        # application window(s).  Use _activate_figure() to set.
219        self.pylab_interface = EmbeddedPylab(canvas)
220
221        # Instantiate the matplotlib navigation toolbar and explicitly show it.
222        mpl_toolbar = Toolbar(canvas, self, False)
223
224        layout = QtWidgets.QVBoxLayout()
225        layout.addWidget(canvas)
226        layout.addWidget(mpl_toolbar)
227        self.setLayout(layout)
228
229    def _is_shown(self):
230        return IS_MAC or self.IsShown()
Note: See TracBrowser for help on using the repository browser.