File: data_view.py

package info (click to toggle)
python-bumps 1.0.0b2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,144 kB
  • sloc: python: 23,941; xml: 493; ansic: 373; makefile: 209; sh: 91; javascript: 90
file content (232 lines) | stat: -rwxr-xr-x 8,187 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as Toolbar

# The Figure object is used to create backend-independent plot representations.
from matplotlib.figure import Figure
from numpy import inf

from ..fitproblem import FitProblem
from .util import EmbeddedPylab

# Can't seem to detect when notebook should be drawn on Mac
IS_MAC = wx.Platform == "__WXMAC__"


# ------------------------------------------------------------------------
class DataView(wx.Panel):
    title = "Data"
    default_size = (600, 400)

    def __init__(self, *args, **kw):
        wx.Panel.__init__(self, *args, **kw)

        # Instantiate a figure object that will contain our plots.
        figure = Figure(figsize=(1, 1), dpi=72)

        # Initialize the figure canvas, mapping the figure object to the plot
        # engine backend.
        canvas = FigureCanvas(self, wx.ID_ANY, figure)

        # Wx-Pylab magic ...
        # Make our canvas an active figure manager for pylab so that when
        # pylab plotting statements are executed they will operate on our
        # canvas and not create a new frame and canvas for display purposes.
        # This technique allows this application to execute code that uses
        # pylab stataments to generate plots and embed these plots in our
        # application window(s).  Use _activate_figure() to set.
        self.pylab_interface = EmbeddedPylab(canvas)

        # Instantiate the matplotlib navigation toolbar and explicitly show it.
        mpl_toolbar = Toolbar(canvas)
        mpl_toolbar.Realize()

        # Create a vertical box sizer to manage the widgets in the main panel.
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(canvas, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, border=0)
        sizer.Add(mpl_toolbar, 0, wx.EXPAND | wx.ALL, border=0)

        # Associate the sizer with its container.
        self.SetSizer(sizer)
        sizer.Fit(self)

        self._need_redraw = False
        self.Bind(wx.EVT_SHOW, self.OnShow)
        self._calculating = False
        self.toolbar = mpl_toolbar
        self.view = "linear"

    def menu(self):
        # Add 'View' menu to the menu bar and define its options.
        # Present y-axis plotting scales as radio buttons.
        # Grey out items that are not currently implemented.
        frame = wx.GetTopLevelParent(self)
        menu = wx.Menu()
        _item = menu.AppendRadioItem(wx.ID_ANY, "Li&near", "Plot y-axis in linear scale")
        _item.Check(True)
        frame.Bind(wx.EVT_MENU, self.OnLinear, _item)
        _item = menu.AppendRadioItem(wx.ID_ANY, "&Log", "Plot y-axis in log scale")
        frame.Bind(wx.EVT_MENU, self.OnLog, _item)

        menu.AppendSeparator()

        _item = menu.Append(wx.ID_ANY, "&Residuals", "Show residuals on plot panel")
        frame.Bind(wx.EVT_MENU, self.OnResiduals, _item)
        menu.Enable(id=_item.GetId(), enable=True)

        return menu

    # ==== Views ====
    # TODO: can probably parameterize the view selection.
    def OnLog(self, event):
        self.view = "log"
        self.redraw()

    def OnLinear(self, event):
        self.view = "linear"
        self.redraw()

    def OnResiduals(self, event):
        self.view = "residual"
        self.redraw()

    # ==== Model view interface ===
    def OnShow(self, event):
        # print "theory show"
        if not event.Show:
            return
        # print "showing theory"
        if self._need_redraw:
            # print "-redraw"
            self.redraw()

    def get_state(self):
        return self.problem

    def set_state(self, state):
        self.set_model(state)

    def set_model(self, model):
        self.problem = model
        self.redraw(reset=True)

    def update_model(self, model):
        if self.problem == model:
            self.redraw()

    def update_parameters(self, model):
        if self.problem == model:
            self.redraw()

    # =============================

    def redraw(self, reset=False):
        # Hold off drawing until the tab is visible
        if not IS_MAC and not self.IsShown():
            self._need_redraw = True
            return
        # print "drawing theory"

        if self._calculating:
            # That means that I've entered the thread through a
            # wx.Yield for the currently executing redraw.  I need
            # to cancel the running thread and force it to start
            # the calculation over.
            self._cancel_calculate = True
            # print "canceling calculation"
            return

        self._need_redraw = False
        self._calculating = True

        # Calculate theory
        # print "calling again"
        while True:
            # print "restarting"
            # We are restarting the calculation, so clear the reset flag
            self._cancel_calculate = False

            # clear graph and exit if problem is not defined
            if self.problem is None:
                with self.pylab_interface as pylab:
                    pylab.clf()  # clear the canvas
                    break

            # Preform the calculation
            if isinstance(self.problem, FitProblem):
                # print "n=",len(self.problem.models)
                for p in self.problem.models:
                    self._precalc(p)
                    # print "cancel",self._cancel_calculate,"reset",p.updating
                    if self._cancel_calculate:
                        break
                if self._cancel_calculate:
                    continue
            else:
                self._precalc(self.problem)
                if self._cancel_calculate:
                    continue

            # Redraw the canvas with newly calculated theory
            # TODO: drawing is 10x too slow!
            with self.pylab_interface as pylab:
                ax = pylab.gca()
                # print "reset",reset, ax.get_autoscalex_on(), ax.get_xlim()
                reset = reset or ax.get_autoscalex_on()
                xrange = ax.get_xlim()
                # print "composing"
                pylab.clf()  # clear the canvas
                # shift=20 if self.view == 'log' else 0
                shift = 0
                if isinstance(self.problem, FitProblem):
                    for i, p in enumerate(self.problem.models):
                        # if hasattr(p.fitness,'plot'):
                        p.plot(view=self.view)
                        if self._cancel_calculate:
                            break
                    pylab.text(0.01, 0.01, "chisq=%s" % self.problem.chisq_str(), transform=pylab.gca().transAxes)
                    if self._cancel_calculate:
                        continue
                else:
                    # if hasattr(self.problem.fitness,'plot'):
                    self.problem.plot(view=self.view)
                    if self._cancel_calculate:
                        continue

                # print "drawing"
                if not reset:
                    self.toolbar.push_current()
                    set_xrange(pylab.gca(), xrange)
                    self.toolbar.push_current()
                pylab.draw()
                # print "done drawing"
                break

        self._calculating = False

    def _precalc(self, problem):
        """
        Calculate each model separately, hopefully not blocking the gui too long.
        Individual problems may want more control, e.g., between computing theory
        and resolution.
        """
        _ = problem.nllf()
        wx.Yield()


def set_xrange(ax, xrange):
    miny, maxy = inf, -inf
    for L in ax.get_lines():
        x, y = L.get_data()
        idx = (x > xrange[0]) & (x < xrange[1])
        if idx.any():
            miny = min(miny, min(y[idx]))
            maxy = max(maxy, max(y[idx]))
    if miny < maxy:
        if ax.get_yscale() == "linear":
            padding = 0.05 * (maxy - miny)
            miny, maxy = miny - padding, maxy + padding
        else:
            miny, maxy = miny * 0.95, maxy * 1.05
    ax.set_xlim(xrange)
    ax.set_ylim(miny, maxy)