root/graph.py

Revision 533:137c1fd5257d, 11.7 KB (checked in by Ian Ward <ian@…>, 12 months ago)

fix graph example for python3

  • Property exe set to *
Line 
1#!/usr/bin/python
2#
3# Urwid graphics example program
4#    Copyright (C) 2004-2011  Ian Ward
5#
6#    This library is free software; you can redistribute it and/or
7#    modify it under the terms of the GNU Lesser General Public
8#    License as published by the Free Software Foundation; either
9#    version 2.1 of the License, or (at your option) any later version.
10#
11#    This library is distributed in the hope that it will be useful,
12#    but WITHOUT ANY WARRANTY; without even the implied warranty of
13#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14#    Lesser General Public License for more details.
15#
16#    You should have received a copy of the GNU Lesser General Public
17#    License along with this library; if not, write to the Free Software
18#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19#
20# Urwid web site: http://excess.org/urwid/
21
22"""
23Urwid example demonstrating use of the BarGraph widget and creating a
24floating-window appearance.  Also shows use of alarms to create timed
25animation.
26"""
27
28import urwid
29
30import math
31import time
32
33UPDATE_INTERVAL = 0.2
34
35def sin100( x ):
36    """
37    A sin function that returns values between 0 and 100 and repeats
38    after x == 100.
39    """
40    return 50 + 50 * math.sin( x * math.pi / 50 )
41   
42class GraphModel:
43    """
44    A class responsible for storing the data that will be displayed
45    on the graph, and keeping track of which mode is enabled.
46    """
47
48    data_max_value = 100
49   
50    def __init__(self):
51        data = [ ('Saw', range(0,100,2)*2),
52            ('Square', [0]*30 + [100]*30),
53            ('Sine 1', [sin100(x) for x in range(100)] ),
54            ('Sine 2', [(sin100(x) + sin100(x*2))/2 
55                for x in range(100)] ),
56            ('Sine 3', [(sin100(x) + sin100(x*3))/2 
57                for x in range(100)] ),
58            ]
59        self.modes = []
60        self.data = {}
61        for m, d in data:
62            self.modes.append(m)
63            self.data[m] = d
64   
65    def get_modes(self):
66        return self.modes
67   
68    def set_mode(self, m):
69        self.current_mode = m
70   
71    def get_data(self, offset, r):
72        """
73        Return the data in [offset:offset+r], the maximum value
74        for items returned, and the offset at which the data
75        repeats.
76        """
77        l = []
78        d = self.data[self.current_mode]
79        while r:
80            offset = offset % len(d)
81            segment = d[offset:offset+r]
82            r -= len(segment)
83            offset += len(segment)
84            l += segment
85        return l, self.data_max_value, len(d)
86
87
88class GraphView(urwid.WidgetWrap):
89    """
90    A class responsible for providing the application's interface and
91    graph display.
92    """
93    palette = [
94        ('body',         'black',      'light gray', 'standout'),
95        ('header',       'white',      'dark red',   'bold'),
96        ('screen edge',  'light blue', 'dark cyan'),
97        ('main shadow',  'dark gray',  'black'),
98        ('line',         'black',      'light gray', 'standout'),
99        ('bg background','light gray', 'black'),
100        ('bg 1',         'black',      'dark blue', 'standout'),
101        ('bg 1 smooth',  'dark blue',  'black'),
102        ('bg 2',         'black',      'dark cyan', 'standout'),
103        ('bg 2 smooth',  'dark cyan',  'black'),
104        ('button normal','light gray', 'dark blue', 'standout'),
105        ('button select','white',      'dark green'),
106        ('line',         'black',      'light gray', 'standout'),
107        ('pg normal',    'white',      'black', 'standout'),
108        ('pg complete',  'white',      'dark magenta'),
109        ('pg smooth',     'dark magenta','black')
110        ]
111       
112    graph_samples_per_bar = 10
113    graph_num_bars = 5
114    graph_offset_per_second = 5
115   
116    def __init__(self, controller):
117        self.controller = controller
118        self.started = True
119        self.start_time = None
120        self.offset = 0
121        self.last_offset = None
122        urwid.WidgetWrap.__init__(self, self.main_window())
123
124    def get_offset_now(self):
125        if self.start_time is None:
126            return 0
127        if not self.started:
128            return self.offset
129        tdelta = time.time() - self.start_time
130        return int(self.offset + (tdelta*self.graph_offset_per_second))
131
132    def update_graph(self, force_update=False):
133        o = self.get_offset_now()
134        if o == self.last_offset and not force_update:
135            return False
136        self.last_offset = o
137        gspb = self.graph_samples_per_bar
138        r = gspb * self.graph_num_bars
139        d, max_value, repeat = self.controller.get_data( o, r )
140        l = []
141        for n in range(self.graph_num_bars):
142            value = sum(d[n*gspb:(n+1)*gspb])/gspb
143            # toggle between two bar types
144            if n & 1:
145                l.append([0,value])
146            else:
147                l.append([value,0])
148        self.graph.set_data(l,max_value)
149       
150        # also update progress
151        if (o//repeat)&1:
152            # show 100% for first half, 0 for second half
153            if o%repeat > repeat//2:
154                prog = 0
155            else:
156                prog = 1
157        else:
158            prog = float(o%repeat) / repeat
159        self.animate_progress.set_completion( prog )
160        return True
161
162    def on_animate_button(self, button):
163        """Toggle started state and button text."""
164        if self.started: # stop animation
165            button.set_label("Start")
166            self.offset = self.get_offset_now()
167            self.started = False
168            self.controller.stop_animation()
169        else:
170            button.set_label("Stop")
171            self.started = True
172            self.start_time = time.time()
173            self.controller.animate_graph()
174           
175   
176    def on_reset_button(self, w):
177        self.offset = 0
178        self.start_time = time.time()
179        self.update_graph(True)
180
181    def on_mode_button(self, button, state):
182        """Notify the controller of a new mode setting."""
183        if state:
184            # The new mode is the label of the button
185            self.controller.set_mode( button.get_label() )
186        self.last_offset = None
187
188    def on_mode_change(self, m):
189        """Handle external mode change by updating radio buttons."""
190        for rb in self.mode_buttons:
191            if rb.get_label() == m:
192                rb.set_state(True, do_callback=False)
193                break
194        self.last_offset = None
195
196    def on_unicode_checkbox(self, w, state):
197        self.graph = self.bar_graph( state )
198        self.graph_wrap._w = self.graph
199        self.animate_progress = self.progress_bar( state )
200        self.animate_progress_wrap._w = self.animate_progress
201        self.update_graph( True )
202       
203
204    def main_shadow(self, w):
205        """Wrap a shadow and background around widget w."""
206        bg = urwid.AttrWrap(urwid.SolidFill(u"\u2592"), 'screen edge')
207        shadow = urwid.AttrWrap(urwid.SolidFill(u" "), 'main shadow')
208       
209        bg = urwid.Overlay( shadow, bg,
210            ('fixed left', 3), ('fixed right', 1),
211            ('fixed top', 2), ('fixed bottom', 1))
212        w = urwid.Overlay( w, bg,
213            ('fixed left', 2), ('fixed right', 3),
214            ('fixed top', 1), ('fixed bottom', 2))
215        return w
216       
217    def bar_graph(self, smooth=False):
218        satt = None
219        if smooth:
220            satt = {(1,0): 'bg 1 smooth', (2,0): 'bg 2 smooth'}
221        w = urwid.BarGraph(['bg background','bg 1','bg 2'], satt=satt)
222        return w
223
224    def button(self, t, fn):
225        w = urwid.Button(t, fn)
226        w = urwid.AttrWrap(w, 'button normal', 'button select')
227        return w
228
229    def radio_button(self, g, l, fn):
230        w = urwid.RadioButton(g, l, False, on_state_change=fn)
231        w = urwid.AttrWrap(w, 'button normal', 'button select')
232        return w
233
234    def progress_bar(self, smooth=False):
235        if smooth:
236            return urwid.ProgressBar('pg normal', 'pg complete', 
237                0, 1, 'pg smooth')
238        else:
239            return urwid.ProgressBar('pg normal', 'pg complete',
240                0, 1)
241
242    def exit_program(self, w):
243        raise urwid.ExitMainLoop()
244
245    def graph_controls(self):
246        modes = self.controller.get_modes()
247        # setup mode radio buttons
248        self.mode_buttons = []
249        group = []
250        for m in modes:
251            rb = self.radio_button( group, m, self.on_mode_button )
252            self.mode_buttons.append( rb )
253        # setup animate button
254        self.animate_button = self.button( "", self.on_animate_button)
255        self.on_animate_button( self.animate_button )
256        self.offset = 0
257        self.animate_progress = self.progress_bar()
258        animate_controls = urwid.GridFlow( [ 
259            self.animate_button,
260            self.button("Reset", self.on_reset_button),
261            ], 9, 2, 0, 'center')
262       
263        if urwid.get_encoding_mode() == "utf8":
264            unicode_checkbox = urwid.CheckBox(
265                "Enable Unicode Graphics",
266                on_state_change=self.on_unicode_checkbox)
267        else:
268            unicode_checkbox = urwid.Text(
269                "UTF-8 encoding not detected")
270           
271        self.animate_progress_wrap = urwid.WidgetWrap(
272            self.animate_progress)
273       
274        l = [    urwid.Text("Mode",align="center"),
275            ] + self.mode_buttons + [
276            urwid.Divider(),
277            urwid.Text("Animation",align="center"),
278            animate_controls,
279            self.animate_progress_wrap,
280            urwid.Divider(),
281            urwid.LineBox( unicode_checkbox ),
282            urwid.Divider(),
283            self.button("Quit", self.exit_program ),
284            ]
285        w = urwid.ListBox(urwid.SimpleListWalker(l))
286        return w
287
288    def main_window(self): 
289        self.graph = self.bar_graph()
290        self.graph_wrap = urwid.WidgetWrap( self.graph )
291        vline = urwid.AttrWrap( urwid.SolidFill(u'\u2502'), 'line')
292        c = self.graph_controls()
293        w = urwid.Columns([('weight',2,self.graph_wrap),
294            ('fixed',1,vline), c],
295            dividechars=1, focus_column=2)
296        w = urwid.Padding(w,('fixed left',1),('fixed right',0))
297        w = urwid.AttrWrap(w,'body')
298        w = urwid.LineBox(w)
299        w = urwid.AttrWrap(w,'line')
300        w = self.main_shadow(w)
301        return w
302       
303
304class GraphController:
305    """
306    A class responsible for setting up the model and view and running
307    the application.
308    """
309    def __init__(self):
310        self.animate_alarm = None
311        self.model = GraphModel()
312        self.view = GraphView( self )
313        # use the first mode as the default
314        mode = self.get_modes()[0]
315        self.model.set_mode( mode )
316        # update the view
317        self.view.on_mode_change( mode )
318        self.view.update_graph(True)
319
320    def get_modes(self):
321        """Allow our view access to the list of modes."""
322        return self.model.get_modes()
323   
324    def set_mode(self, m):
325        """Allow our view to set the mode."""
326        rval = self.model.set_mode( m )
327        self.view.update_graph(True)
328        return rval
329   
330    def get_data(self, offset, range):
331        """Provide data to our view for the graph."""
332        return self.model.get_data( offset, range )
333   
334
335    def main(self):
336        self.loop = urwid.MainLoop(self.view, self.view.palette)
337        self.loop.run()
338
339    def animate_graph(self, loop=None, user_data=None):
340        """update the graph and schedule the next update"""
341        self.view.update_graph()
342        self.animate_alarm = self.loop.set_alarm_in(
343            UPDATE_INTERVAL, self.animate_graph)
344
345    def stop_animation(self):
346        """stop animating the graph"""
347        if self.animate_alarm:
348            self.loop.remove_alarm(self.animate_alarm)
349        self.animate_alarm = None
350   
351
352def main():
353    GraphController().main()
354   
355if '__main__'==__name__:
356    main()
Note: See TracBrowser for help on using the browser.