2
Sample visualization of simulated live data stream
4
Shows how Chaco and Traits can be used to easily build a data
5
acquisition and visualization system.
7
Two frames are opened: one has the plot and allows configuration of
8
various plot properties, and one which simulates controls for the hardware
9
device from which the data is being acquired; in this case, it is a mockup
10
random number generator whose mean and standard deviation can be controlled
14
# Major library imports
17
from numpy import arange, array, hstack, random
20
from traits.api import Array, Bool, Callable, Enum, Float, HasTraits, \
22
from traitsui.api import Group, HGroup, Item, View, spring, Handler
23
from pyface.timer.api import Timer
26
from chaco.chaco_plot_editor import ChacoPlotItem
29
class Viewer(HasTraits):
30
""" This class just contains the two data arrays that will be updated
31
by the Controller. The visualization/editor for this class is a
39
plot_type = Enum("line", "scatter")
41
# This "view" attribute defines how an instance of this class will
42
# be displayed when .edit_traits() is called on it. (See MyApp.OnInit()
44
view = View(ChacoPlotItem("index", "data",
45
type_trait="plot_type",
53
padding_bg_color="lightgray",
58
HGroup(spring, Item("plot_type", style='custom'), spring),
61
width=800, height=500)
64
class Controller(HasTraits):
66
# A reference to the plot viewer object
67
viewer = Instance(Viewer)
69
# Some parameters controller the random signal that will be generated
70
distribution_type = Enum("normal", "lognormal")
74
# The max number of data points to accumulate and show in the plot
75
max_num_points = Int(100)
77
# The number of data points we have received; we need to keep track of
78
# this in order to generate the correct x axis data series.
81
# private reference to the random number generator. this syntax
82
# just means that self._generator should be initialized to
83
# random.normal, which is a random number function, and in the future
84
# it can be set to any callable object.
85
_generator = Trait(random.normal, Callable)
87
view = View(Group('distribution_type',
91
orientation="vertical"),
92
buttons=["OK", "Cancel"])
94
def timer_tick(self, *args):
95
""" Callback function that should get called based on a wx timer
96
tick. This will generate a new random datapoint and set it on
97
the .data array of our viewer object.
99
# Generate a new number and increment the tick count
100
new_val = self._generator(self.mean, self.stddev)
103
# grab the existing data, truncate it, and append the new point.
104
# This isn't the most efficient thing in the world but it works.
105
cur_data = self.viewer.data
106
new_data = hstack((cur_data[-self.max_num_points+1:], [new_val]))
107
new_index = arange(self.num_ticks - len(new_data) + 1, self.num_ticks+0.01)
109
self.viewer.index = new_index
110
self.viewer.data = new_data
113
def _distribution_type_changed(self):
114
# This listens for a change in the type of distribution to use.
115
if self.distribution_type == "normal":
116
self._generator = random.normal
118
self._generator = random.lognormal
120
#===============================================================================
121
# # Demo class that is used by the demo.py application.
122
#===============================================================================
123
# NOTE: The Demo class is being created for the purpose of running this
124
# example using a TraitsDemo-like app (see examples/demo/demo.py in Traits3).
125
# The demo.py file looks for a 'demo' or 'popup' or 'modal popup' keyword
126
# when it executes this file, and displays a view for it.
128
class DemoHandler(Handler):
130
def closed(self, info, is_ok):
131
""" Handles a dialog-based user interface being closed by the user.
132
Overridden here to stop the timer once the window is destroyed.
135
info.object.timer.Stop()
138
class Demo(HasTraits):
139
controller = Instance(Controller)
140
viewer = Instance(Viewer, ())
141
timer = Instance(Timer)
142
view = View(Item('controller', style='custom', show_label=False),
143
Item('viewer', style='custom', show_label=False),
144
handler = DemoHandler,
147
def edit_traits(self, *args, **kws):
148
# Start up the timer! We should do this only when the demo actually
149
# starts and not when the demo object is created.
150
self.timer=Timer(100, self.controller.timer_tick)
151
return super(Demo, self).edit_traits(*args, **kws)
153
def configure_traits(self, *args, **kws):
154
# Start up the timer! We should do this only when the demo actually
155
# starts and not when the demo object is created.
156
self.timer=Timer(100, self.controller.timer_tick)
157
return super(Demo, self).configure_traits(*args, **kws)
159
def _controller_default(self):
160
return Controller(viewer=self.viewer)
164
# wxApp used when this file is run from the command line.
166
class MyApp(wx.PySimpleApp):
168
def OnInit(self, *args, **kw):
170
controller = Controller(viewer = viewer)
172
# Pop up the windows for the two objects
174
controller.edit_traits()
176
# Set up the timer and start it up
177
self.setup_timer(controller)
181
def setup_timer(self, controller):
182
# Create a new WX timer
184
self.timer = wx.Timer(self, timerId)
186
# Register a callback with the timer event
187
self.Bind(wx.EVT_TIMER, controller.timer_tick, id=timerId)
189
# Start up the timer! We have to tell it how many milliseconds
190
# to wait between timer events. For now we will hardcode it
191
# to be 100 ms, so we get 10 points per second.
192
self.timer.Start(100.0, wx.TIMER_CONTINUOUS)
196
# This is called when this example is to be run in a standalone mode.
197
if __name__ == "__main__":