~ubuntu-branches/ubuntu/utopic/python-chaco/utopic

« back to all changes in this revision

Viewing changes to docs/source/user_manual/chaco_tutorial.rst

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2014-06-01 17:04:08 UTC
  • mfrom: (7.2.5 sid)
  • Revision ID: package-import@ubuntu.com-20140601170408-m86xvdjd83a4qon0
Tags: 4.4.1-1ubuntu1
* Merge from Debian unstable. Remaining Ubuntu changes:
 - Let the binary-predeb target work on the usr/lib/python* directory
   as we don't have usr/share/pyshared anymore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
 
2
 
.. highlight:: python
3
 
   :linenothreshold: 10
4
 
   
5
 
.. _tutorial:
6
 
 
7
 
###############################
8
 
Interactive plotting with Chaco
9
 
###############################
10
 
 
11
 
Overview
12
 
========
13
 
 
14
 
This tutorial is an introduction to Chaco. We're going to build several
15
 
mini-applications of increasing capability and complexity. Chaco was designed to
16
 
be used primarily by scientific programmers, and this tutorial requires only
17
 
basic familiarity with Python.
18
 
 
19
 
Knowledge of NumPy can be helpful for certain parts of the tutorial. Knowledge
20
 
of GUI programming concepts such as widgets, windows, and events are helpful
21
 
for the last portion of the tutorial, but it is not required.
22
 
 
23
 
This tutorial demonstrates using Chaco with Traits UI, so knowledge of the
24
 
Traits framework is also helpful. We don't use very many sophisticated aspects
25
 
of Traits or Traits UI, and it is entirely possible to pick it up as you go
26
 
through the tutorial. This tutorial applies to Enthought Tool Suite
27
 
version 3.x.
28
 
 
29
 
It's also worth pointing out that you don't *have* to use Traits UI in order to
30
 
use Chaco --- you can integrate Chaco directly with Qt or wxPython --- but for
31
 
this tutorial, we use Traits UI to make things easier.
32
 
 
33
 
 
34
 
Goals
35
 
=====
36
 
 
37
 
By the end of this tutorial, you will have learned how to:
38
 
 
39
 
- create plots of various types
40
 
 
41
 
- arrange plots in various layouts
42
 
 
43
 
- configure and dynamically modify your plots using Traits UI
44
 
 
45
 
- interact with plots using tools
46
 
 
47
 
- create custom, stateful tools that interact with mouse and keyboard
48
 
 
49
 
 
50
 
Introduction
51
 
============
52
 
 
53
 
Chaco is a *plotting application toolkit*. This means that it can build
54
 
both static plots and dynamic data visualizations that let you
55
 
interactively explore your data. Here are four basic examples of Chaco plots:
56
 
 
57
 
.. image:: images/tornado.png
58
 
    :height: 300pt
59
 
 
60
 
This plot shows a static "tornado plot" with a categorical Y axis and continuous
61
 
X axis.  The plot is resizable, but the user cannot interact or explore the data
62
 
in any way.
63
 
 
64
 
.. image:: images/simple_line.png
65
 
    :height: 300pt
66
 
 
67
 
This is an overlaid composition of line and scatter plots with a legend. Unlike
68
 
the previous plot, the user can pan and zoom this plot, exploring the
69
 
relationship between data curves in areas that appear densely overlapping.
70
 
Furthermore, the user can move the legend to an arbitrary position on the plot,
71
 
and as they resize the plot, the legend maintains the same screen-space
72
 
separation relative to its closest corner.
73
 
 
74
 
.. image:: images/regression.png
75
 
    :height: 300pt
76
 
 
77
 
This example starts to demonstrate interacting with the data set in an
78
 
exploratory way. Whereas interactivity in the previous example was limited to
79
 
basic pan and zoom (which are fairly common in most plotting libraries), this is
80
 
an example of a more advanced interaction that allows a level of data
81
 
exploration beyond the standard view manipulations.
82
 
 
83
 
With this example, the user can select a region of data space, and a simple
84
 
line fit is applied to the selected points. The equation of the line is
85
 
then displayed in a text label.
86
 
 
87
 
The lasso selection tool and regression overlay are both built in to Chaco,
88
 
but they serve an additional purpose of demonstrating how one can build complex
89
 
data-centric interactions and displays on top of the Chaco framework.
90
 
 
91
 
.. image:: ../images/scalar_function.png
92
 
    :height: 350pt
93
 
 
94
 
This is a much more complex demonstration of Chaco's capabilities.  The user
95
 
can view the cross sections of a 2-D scalar-valued function.  The cross sections
96
 
update in real time as the user moves the mouse, and the "bubble" on each line
97
 
plot represents the location of the cursor along that dimension.  By using
98
 
drop-down menus (not show here), the user can change plot attributes like the
99
 
colormap and the number of contour levels used in the center plot, as well as
100
 
the actual function being plotted.
101
 
 
102
 
 
103
 
Script-oriented plotting
104
 
========================
105
 
 
106
 
We distinguish between "static" plots and "interactive visualizations"
107
 
because these different applications of a library affect the structure
108
 
of how the library is written, as well as the code you write to use the
109
 
library.
110
 
 
111
 
Here is a simple example of the "script-oriented" approach for creating
112
 
a static plot.  This is probably familiar to anyone who has used Gnuplot,
113
 
MATLAB, or Matplotlib::
114
 
 
115
 
    import numpy as np
116
 
    from chaco.shell import *
117
 
 
118
 
    x = np.linspace(-2*pi, 2*pi, 100)
119
 
    y = np.sin(x)
120
 
 
121
 
    plot(x, y, "r-")
122
 
    title("First plot")
123
 
    ytitle("sin(x)")
124
 
    show()
125
 
 
126
 
This creates this plot:
127
 
 
128
 
.. image:: images/script_oriented.png
129
 
    :height: 300pt
130
 
 
131
 
The basic structure of this example is that we generate some data, then we call
132
 
functions to plot the data and configure the plot. There is a global concept of
133
 
"the active plot", and the functions do high-level manipulations on it. The
134
 
generated plot is then usually saved to disk for inclusion in a journal article
135
 
or presentation slides.
136
 
 
137
 
Now, as it so happens, this particular example uses the `chaco.shell`
138
 
script plotting package, so when you run this script, the plot that Chaco opens
139
 
does have some basic interactivity. You can pan and zoom, and even move forwards
140
 
and backwards through your zoom history. But ultimately it's a pretty static
141
 
view into the data.
142
 
 
143
 
 
144
 
.. _line_plot_example:
145
 
 
146
 
Application-oriented plotting
147
 
=============================
148
 
 
149
 
The second approach to plotting can be thought of as "application-oriented", for
150
 
lack of a better term. There is definitely a bit more code, and the plot
151
 
initially doesn't look much different, but it sets us up to do more interesting
152
 
things, as you will see later on::
153
 
 
154
 
    from traits.api import HasTraits, Instance
155
 
    from traitsui.api import View, Item
156
 
    from chaco.api import Plot, ArrayPlotData
157
 
    from enable.component_editor import ComponentEditor
158
 
    from numpy import linspace, sin
159
 
 
160
 
    class LinePlot(HasTraits):
161
 
        plot = Instance(Plot)
162
 
 
163
 
        traits_view = View(
164
 
            Item('plot',editor=ComponentEditor(), show_label=False), 
165
 
            width=500, height=500, resizable=True, title="Chaco Plot")
166
 
 
167
 
        def __init__(self):
168
 
            super(LinePlot, self).__init__()
169
 
 
170
 
            x = linspace(-14, 14, 100)
171
 
            y = sin(x) * x**3
172
 
            plotdata = ArrayPlotData(x=x, y=y)
173
 
 
174
 
            plot = Plot(plotdata)
175
 
            plot.plot(("x", "y"), type="line", color="blue")
176
 
            plot.title = "sin(x) * x^3"
177
 
 
178
 
            self.plot = plot
179
 
 
180
 
    if __name__ == "__main__":
181
 
        LinePlot().configure_traits()
182
 
 
183
 
 
184
 
This produces a plot similar to the previous script-oriented code snippet:
185
 
 
186
 
.. image:: images/first_plot.png
187
 
    :height: 300pt
188
 
 
189
 
So, this is our first "real" Chaco plot. We will walk through this code and
190
 
look at what each bit does.  This example serves as the basis for many of the
191
 
later examples.
192
 
 
193
 
 
194
 
Application-oriented plotting, step by step
195
 
===========================================
196
 
 
197
 
Let's start with the basics.  First, we declare a class to represent our
198
 
plot, called :class:`LinePlot`::
199
 
 
200
 
    class LinePlot(HasTraits):
201
 
        plot = Instance(Plot)
202
 
 
203
 
This class uses the Enthought Traits package, and all of our objects subclass
204
 
from :class:`HasTraits`.
205
 
 
206
 
Next, we declare a Traits UI View for this class::
207
 
 
208
 
    traits_view = View( 
209
 
            Item('plot',editor=ComponentEditor(), show_label=False), 
210
 
            width=500, height=500, resizable=True, title="Chaco Plot") 
211
 
 
212
 
Inside this view, we are placing a reference to the :attr:`plot` trait and
213
 
telling Traits UI to use the :class:`ComponentEditor` (imported from 
214
 
:mod:`enable.component_editor`) to display it. If the
215
 
trait were an Int or Str or Float, Traits could automatically pick an 
216
 
appropriate GUI element to display it. Since Traits UI doesn't natively know 
217
 
how to display Chaco components, we explicitly tell it what kind of editor to
218
 
use.
219
 
 
220
 
The other parameters in the :class:`View` constructor are pretty
221
 
self-explanatory, and the 
222
 
`Traits UI User's Guide <http://code.enthought.com/projects/traits/docs/html/TUIUG/index.html>`_ 
223
 
documents all the various properties
224
 
you can set here. For our purposes, this Traits View is sort of boilerplate. It
225
 
gets us a nice little window that we can resize. We'll be using something like
226
 
this View in most of the examples in the rest of the tutorial.
227
 
 
228
 
Now, let's look at the constructor, where the real work gets done::
229
 
 
230
 
    def __init__(self): 
231
 
        super(LinePlot, self).__init__()
232
 
        x = linspace(-14, 14, 100) 
233
 
        y = sin(x) * x**3 
234
 
        plotdata = ArrayPlotData(x=x, y=y) 
235
 
 
236
 
The first thing we do here is call the super-class's :meth:`__init__` method,
237
 
which ensures that all the Traits machinery is properly set up, even though the
238
 
:meth:`__init__` method is overridden. Then we create some mock data, just like
239
 
in the script-oriented approach. But rather than directly calling some sort of
240
 
plotting function to throw up a plot, we create this :class:`ArrayPlotData`
241
 
object and stick the data in there. The ArrayPlotData object is a simple
242
 
structure that associates a name with a NumPy array.
243
 
 
244
 
In a script-oriented approach to plotting, whenever you have to update the data
245
 
or tweak any part of the plot, you basically re-run the entire script.  Chaco's
246
 
model is based on having objects representing each of the little pieces of a
247
 
plot, and they all use Traits events to notify one another that some attribute
248
 
has changed.  So, the ArrayPlotData is an object that interfaces your
249
 
data with the rest of the objects in the plot.  In a later example we'll see
250
 
how we can use the ArrayPlotData to quickly swap data items in and
251
 
out, without affecting the rest of the plot.
252
 
 
253
 
The next line creates an actual :class:`Plot` object, and gives it the
254
 
ArrayPlotData instance we created previously::
255
 
 
256
 
    plot = Plot(plotdata)
257
 
 
258
 
Chaco's Plot object serves two roles: it is both a container of
259
 
renderers, which are the objects that do the actual task of transforming data
260
 
into lines and markers and colors on the screen, and it is a factory for
261
 
instantiating renderers. Once you get more familiar with Chaco, you can choose
262
 
to not use the Plot object, and instead directly create renderers and containers
263
 
manually. Nonetheless, the Plot object does a lot of nice housekeeping that is
264
 
useful in a large majority of use cases.
265
 
 
266
 
Next, we call the :meth:`plot` method on the Plot object we just created::
267
 
 
268
 
    plot.plot(("x", "y"), type="line", color="blue")
269
 
 
270
 
This creates a blue line plot of the data items named "x" and "y".  Note that
271
 
we are not passing in an actual array here; we are passing in the names of arrays
272
 
in the ArrayPlotData we created previously.
273
 
 
274
 
This method call creates a new renderer --- in this case a line renderer --- and
275
 
adds it to the Plot.
276
 
 
277
 
This may seem kind of redundant or roundabout to folks who are used to passing
278
 
in a pile of NumPy arrays to a plot function, but consider this:
279
 
ArrayPlotData objects can be shared between multiple Plots.  If you
280
 
want several different plots of the same data, you don't have to externally
281
 
keep track of which plots are holding on to identical copies of what data, and
282
 
then remember to shove in new data into every single one of those plots.  The
283
 
ArrayPlotData object acts almost like a symlink between consumers of data and
284
 
the actual data itself.
285
 
 
286
 
Next, we set a title on the plot::
287
 
 
288
 
    plot.title = "sin(x) * x^3"
289
 
 
290
 
And then we set our :attr:`plot` trait to the new plot::
291
 
 
292
 
    self.plot = plot
293
 
 
294
 
The last thing we do in this script is set up some code to run when the script
295
 
is executed::
296
 
 
297
 
    if __name__ == "__main__": 
298
 
        LinePlot().configure_traits() 
299
 
 
300
 
This one-liner instantiates a LinePlot object and calls its
301
 
:meth:`configure_traits` method.  This brings up a dialog with a traits editor for
302
 
the object, built up according to the View we created earlier.  In our
303
 
case, the editor just displays our :attr:`plot` attribute using the
304
 
ComponentEditor.
305
 
 
306
 
 
307
 
Scatter plots
308
 
=============
309
 
 
310
 
We can use the same pattern to build a scatter plot::
311
 
 
312
 
    from traits.api import HasTraits, Instance
313
 
    from traitsui.api import View, Item
314
 
    from chaco.api import Plot, ArrayPlotData
315
 
    from enable.component_editor import ComponentEditor
316
 
    from numpy import linspace, sin
317
 
    
318
 
    class ScatterPlot(HasTraits):
319
 
        plot = Instance(Plot)
320
 
 
321
 
        traits_view = View(
322
 
            Item('plot',editor=ComponentEditor(), show_label=False), 
323
 
            width=500, height=500, resizable=True, title="Chaco Plot")
324
 
 
325
 
        def __init__(self):
326
 
            super(ScatterPlot, self).__init__()
327
 
 
328
 
            x = linspace(-14, 14, 100)
329
 
            y = sin(x) * x**3
330
 
            plotdata = ArrayPlotData(x = x, y = y)
331
 
 
332
 
            plot = Plot(plotdata)
333
 
            plot.plot(("x", "y"), type="scatter", color="blue")
334
 
            plot.title = "sin(x) * x^3"
335
 
 
336
 
            self.plot = plot
337
 
 
338
 
    if __name__ == "__main__":
339
 
        ScatterPlot().configure_traits()
340
 
 
341
 
Note that we have only changed the *type* argument to the :meth:`plot.plot` call
342
 
and the name of the class from :class:`LinePlot` to :class:`ScatterPlot`. This
343
 
produces the following:
344
 
 
345
 
.. image:: images/scatter.png
346
 
    :height: 300pt
347
 
 
348
 
Image plots
349
 
===========
350
 
 
351
 
Image plots can be created in a similar fashion::
352
 
 
353
 
    from traits.api import HasTraits, Instance
354
 
    from traitsui.api import View, Item
355
 
    from chaco.api import Plot, ArrayPlotData, jet
356
 
    from enable.component_editor import ComponentEditor
357
 
    from numpy import exp, linspace, meshgrid
358
 
    
359
 
    class ImagePlot(HasTraits):
360
 
        plot = Instance(Plot)
361
 
 
362
 
        traits_view = View(
363
 
            Item('plot', editor=ComponentEditor(), show_label=False),
364
 
            width=500, height=500, resizable=True, title="Chaco Plot")
365
 
            
366
 
        def __init__(self):
367
 
            super(ImagePlot, self).__init__()
368
 
 
369
 
            x = linspace(0, 10, 50)
370
 
            y = linspace(0, 5, 50)
371
 
            xgrid, ygrid = meshgrid(x, y)
372
 
            z = exp(-(xgrid*xgrid+ygrid*ygrid)/100)
373
 
            plotdata = ArrayPlotData(imagedata = z)
374
 
 
375
 
            plot = Plot(plotdata)
376
 
            plot.img_plot("imagedata", colormap=jet)
377
 
 
378
 
            self.plot = plot
379
 
            
380
 
    if __name__ == "__main__":
381
 
        ImagePlot().configure_traits()
382
 
 
383
 
 
384
 
There are a few more steps to create the input Z data, and we also call a
385
 
different method on the Plot object --- :meth:`img_plot` instead of
386
 
:meth:`plot`.  The details of the method parameters are not that important
387
 
right now; this is just to demonstrate how we can apply the same basic pattern
388
 
from the "first plot" example above to do other kinds of plots.
389
 
 
390
 
.. image:: images/image_plot.png
391
 
    :height: 300pt
392
 
 
393
 
 
394
 
Multiple plots
395
 
==============
396
 
 
397
 
Earlier we said that the Plot object is both a container of renderers and a
398
 
factory (or generator) of renderers. This modification of the previous example
399
 
illustrates this point. We only create a single instance of Plot, but we call
400
 
its :meth:`plot()` method twice. Each call creates a new renderer and adds it to
401
 
the Plot object's list of renderers. Also notice that we are reusing the *x*
402
 
array from the ArrayPlotData::
403
 
 
404
 
    from traits.api import HasTraits, Instance
405
 
    from traitsui.api import View, Item
406
 
    from chaco.api import Plot, ArrayPlotData
407
 
    from enable.component_editor import ComponentEditor
408
 
    from numpy import cos, linspace, sin
409
 
 
410
 
    class OverlappingPlot(HasTraits): 
411
 
 
412
 
        plot = Instance(Plot)
413
 
 
414
 
        traits_view = View(
415
 
            Item('plot',editor=ComponentEditor(), show_label=False), 
416
 
            width=500, height=500, resizable=True, title="Chaco Plot") 
417
 
 
418
 
        def __init__(self):
419
 
            super(OverlappingPlot, self).__init__()
420
 
 
421
 
            x = linspace(-14, 14, 100)
422
 
            y = x/2 * sin(x) 
423
 
            y2 = cos(x) 
424
 
            plotdata = ArrayPlotData(x=x, y=y, y2=y2) 
425
 
 
426
 
            plot = Plot(plotdata)
427
 
            plot.plot(("x", "y"), type="scatter", color="blue") 
428
 
            plot.plot(("x", "y2"), type="line", color="red") 
429
 
 
430
 
            self.plot = plot
431
 
 
432
 
    if __name__ == "__main__":
433
 
        OverlappingPlot().configure_traits()
434
 
 
435
 
This code generates the following plot:
436
 
 
437
 
.. image:: images/overlapping_plot.png
438
 
    :height: 300pt
439
 
 
440
 
 
441
 
Containers
442
 
==========
443
 
 
444
 
So far we've only seen single plots, but frequently we need to plot data side
445
 
by side.  Chaco uses various subclasses of :class:`Container` to do layout.
446
 
Horizontal containers (:class:`HPlotContainer`) place components horizontally:
447
 
 
448
 
.. image:: images/hplotcontainer.png
449
 
    :height: 350pt
450
 
 
451
 
Vertical containers (:class:`VPlotContainer`) array component vertically:
452
 
 
453
 
.. image:: images/vplotcontainer.png
454
 
    :height: 350pt
455
 
 
456
 
Grid container (:class:`GridPlotContainer`) lays plots out in a grid:
457
 
 
458
 
.. image:: images/gridcontainer.png
459
 
    :height: 350pt
460
 
 
461
 
Overlay containers (:class:`OverlayPlotContainer`) just overlay plots on top of
462
 
each other:
463
 
 
464
 
.. image:: images/simple_line.png
465
 
    :height: 350pt
466
 
 
467
 
You've actually already seen OverlayPlotContainer --- the Plot
468
 
class is actually a special subclass of OverlayPlotContainer.  All of
469
 
the plots inside this container appear to share the same X- and Y-axis, but this
470
 
is not a requirement of the container.  For instance, the following plot shows
471
 
plots sharing only the X-axis:
472
 
 
473
 
.. image:: images/multiyaxis.png
474
 
    :height: 350pt
475
 
 
476
 
 
477
 
Using a container
478
 
=================
479
 
 
480
 
Containers can have any Chaco component added to them.  The following code
481
 
creates a separate Plot instance for the scatter plot and the line
482
 
plot, and adds them both to the HPlotContainer object::
483
 
 
484
 
    from traits.api import HasTraits, Instance
485
 
    from traitsui.api import View, Item
486
 
    from chaco.api import HPlotContainer, ArrayPlotData, Plot
487
 
    from enable.component_editor import ComponentEditor
488
 
    from numpy import linspace, sin
489
 
    
490
 
    class ContainerExample(HasTraits): 
491
 
 
492
 
        plot = Instance(HPlotContainer)
493
 
 
494
 
        traits_view = View(Item('plot', editor=ComponentEditor(), show_label=False),
495
 
                           width=1000, height=600, resizable=True, title="Chaco Plot") 
496
 
 
497
 
        def __init__(self):
498
 
            super(ContainerExample, self).__init__()
499
 
 
500
 
            x = linspace(-14, 14, 100)
501
 
            y = sin(x) * x**3 
502
 
            plotdata = ArrayPlotData(x=x, y=y) 
503
 
 
504
 
            scatter = Plot(plotdata)
505
 
            scatter.plot(("x", "y"), type="scatter", color="blue") 
506
 
 
507
 
            line = Plot(plotdata)
508
 
            line.plot(("x", "y"), type="line", color="blue") 
509
 
 
510
 
            container = HPlotContainer(scatter, line)
511
 
            self.plot = container
512
 
 
513
 
    if __name__ == "__main__": 
514
 
        ContainerExample().configure_traits()
515
 
 
516
 
 
517
 
This produces the following plot:
518
 
 
519
 
.. image:: images/container_example.png
520
 
    :height: 300pt
521
 
 
522
 
 
523
 
There are many parameters you can configure on a container, like background
524
 
color, border thickness, spacing, and padding.  We insert some more
525
 
lines between lines 20 and 21 of the previous example to make the two plots
526
 
touch in the middle:
527
 
 
528
 
.. code-block:: python
529
 
 
530
 
            container = HPlotContainer(scatter, line)
531
 
            container.spacing = 0
532
 
 
533
 
            scatter.padding_right = 0
534
 
 
535
 
            line.padding_left = 0
536
 
            line.y_axis.orientation = "right"
537
 
 
538
 
            self.plot = container
539
 
 
540
 
Something to note here is that all Chaco components have both bounds and
541
 
padding (or margin).  In order to make our plots touch, we need to zero out the
542
 
padding on the appropriate side of each plot.  We also move the Y-axis for the
543
 
line plot (which is on the right hand side) to the right side.
544
 
 
545
 
This produces the following:
546
 
 
547
 
.. image:: images/container_nospace.png
548
 
    :height: 300pt
549
 
 
550
 
 
551
 
Dynamically changing plots
552
 
==========================
553
 
 
554
 
So far, the stuff you've seen is pretty standard: building up a plot of some
555
 
sort and doing some layout on them.  Now we start taking advantage
556
 
of the underlying framework.
557
 
 
558
 
Chaco is written using Traits.  This means that all the graphical bits you
559
 
see --- and many of the bits you don't see --- are all objects with various
560
 
traits, generating events, and capable of responding to events.
561
 
 
562
 
We're going to modify our previous ScatterPlot example to demonstrate some
563
 
of these capabilities.  Here is the full listing of the modified code::
564
 
 
565
 
    from traits.api import HasTraits, Instance, Int
566
 
    from traitsui.api import View, Group, Item
567
 
    from enable.api import ColorTrait
568
 
    from enable.component_editor import ComponentEditor
569
 
    from chaco.api import marker_trait, Plot, ArrayPlotData
570
 
    from numpy import linspace, sin
571
 
 
572
 
    class ScatterPlotTraits(HasTraits):
573
 
    
574
 
        plot = Instance(Plot)
575
 
        color = ColorTrait("blue")
576
 
        marker = marker_trait
577
 
        marker_size = Int(4)
578
 
    
579
 
        traits_view = View(
580
 
            Group(Item('color', label="Color", style="custom"),
581
 
                  Item('marker', label="Marker"),
582
 
                  Item('marker_size', label="Size"),
583
 
                  Item('plot', editor=ComponentEditor(), show_label=False),
584
 
                       orientation = "vertical"),
585
 
                  width=800, height=600, resizable=True, title="Chaco Plot")
586
 
    
587
 
        def __init__(self):
588
 
            super(ScatterPlotTraits, self).__init__()
589
 
 
590
 
            x = linspace(-14, 14, 100)
591
 
            y = sin(x) * x**3
592
 
            plotdata = ArrayPlotData(x = x, y = y)
593
 
 
594
 
            plot = Plot(plotdata)
595
 
 
596
 
            self.renderer = plot.plot(("x", "y"), type="scatter", color="blue")[0]
597
 
            self.plot = plot
598
 
    
599
 
        def _color_changed(self):
600
 
            self.renderer.color = self.color
601
 
    
602
 
        def _marker_changed(self):
603
 
            self.renderer.marker = self.marker
604
 
    
605
 
        def _marker_size_changed(self):
606
 
            self.renderer.marker_size = self.marker_size
607
 
    
608
 
    if __name__ == "__main__":
609
 
        ScatterPlotTraits().configure_traits()
610
 
 
611
 
 
612
 
Let's step through the changes.
613
 
 
614
 
First, we add traits for color, marker type, and marker size::
615
 
 
616
 
    class ScatterPlotTraits(HasTraits): 
617
 
        plot = Instance(Plot) 
618
 
        color = ColorTrait("blue") 
619
 
        marker = marker_trait 
620
 
        marker_size = Int(4) 
621
 
 
622
 
We also change our Traits UI View to include references to these
623
 
new traits.  We put them in a Traits UI :class:`Group` so that we can control
624
 
the layout in the dialog a little better --- here, we're setting the layout
625
 
orientation of the elements in the dialog to "vertical". ::
626
 
 
627
 
    traits_view = View( 
628
 
        Group( 
629
 
            Item('color', label="Color", style="custom"), 
630
 
            Item('marker', label="Marker"), 
631
 
            Item('marker_size', label="Size"), 
632
 
            Item('plot', editor=ComponentEditor(), show_label=False), 
633
 
                 orientation = "vertical" ), 
634
 
            width=500, height=500, resizable=True, 
635
 
            title="Chaco Plot")
636
 
 
637
 
Now we have to do something with those traits.  We modify the
638
 
constructor so that we grab a handle to the renderer that is created by
639
 
the call to :meth:`plot`::
640
 
 
641
 
    self.renderer = plot.plot(("x", "y"), type="scatter", color="blue")[0]
642
 
 
643
 
Recall that a Plot is a container for renderers and a factory for them. When
644
 
called, its :meth:`plot` method returns a list of the renderers that the call
645
 
created. In previous examples we've been just ignoring or discarding the return
646
 
value, since we had no use for it. In this case, however, we grab a
647
 
reference to that renderer so that we can modify its attributes in later
648
 
methods.
649
 
 
650
 
The :meth:`plot` method returns a list of renderers because for some values
651
 
of the *type* argument, it will create multiple renderers.  In our case here,
652
 
we are just doing a scatter plot, and this creates just a single renderer.
653
 
 
654
 
Next, we define some Traits event handlers.  These are specially-named
655
 
methods that are called whenever the value of a particular trait changes.  Here
656
 
is the handler for :attr:`color` trait::
657
 
 
658
 
    def _color_changed(self):
659
 
        self.renderer.color = self.color
660
 
 
661
 
This event handler is called whenever the value of :attr:`self.color` changes,
662
 
whether due to user interaction with a GUI, or due to code elsewhere. (The
663
 
Traits framework automatically calls this method because its name follows the
664
 
name template of :samp:`\_{traitname}_changed`.) Since this method is called
665
 
after the new value has already been updated, we can read out the new value just
666
 
by accessing :attr:`self.color`. We just copy the color to the scatter renderer.
667
 
You can see why we needed to hold on to the renderer in the constructor.
668
 
 
669
 
Now we do the same thing for the marker type and marker size traits::
670
 
 
671
 
    def _marker_changed(self):
672
 
        self.renderer.marker = self.marker
673
 
 
674
 
    def _marker_size_changed(self):
675
 
        self.renderer.marker_size = self.marker_size
676
 
 
677
 
Running the code produces an app that looks like this:
678
 
 
679
 
.. image:: images/traits.png
680
 
    :height: 350pt
681
 
 
682
 
Depending on your platform, the color editor/swatch at the top may look different.
683
 
This is how it looks on Mac OS X.  All of the controls here are "live".  If you
684
 
modify them, the plot updates.
685
 
 
686
 
 
687
 
.. _data_chooser_example:
688
 
 
689
 
Dynamically changing plot content
690
 
=================================
691
 
 
692
 
Traits are not just useful for tweaking visual features. For instance, you can
693
 
use them to select among several data items. This next example is based on
694
 
the earlier :ref:`LinePlot example <line_plot_example>`, and we’ll walk through the modifications: ::
695
 
 
696
 
    from scipy.special import jn
697
 
 
698
 
    class DataChooser(HasTraits):
699
 
 
700
 
        plot = Instance(Plot)
701
 
 
702
 
        data_name = Enum("jn0", "jn1", "jn2")
703
 
 
704
 
        traits_view = View(
705
 
            Item('data_name', label="Y data"),
706
 
            Item('plot', editor=ComponentEditor(), show_label=False),
707
 
            width=800, height=600, resizable=True,
708
 
            title="Data Chooser")
709
 
 
710
 
        def __init__(self):
711
 
            x = linspace(-5, 10, 100)
712
 
 
713
 
            # jn is the Bessel function
714
 
            self.data = {"jn0": jn(0, x),
715
 
                         "jn1": jn(1, x),
716
 
                         "jn2": jn(2, x)}
717
 
 
718
 
            self.plotdata = ArrayPlotData(x = x, y = self.data["jn0"])
719
 
 
720
 
            plot = Plot(self.plotdata)
721
 
            plot.plot(("x", "y"), type="line", color="blue")
722
 
            self.plot = plot
723
 
 
724
 
        def _data_name_changed(self):
725
 
            self.plotdata.set_data("y", self.data[self.data_name])
726
 
 
727
 
 
728
 
First, we add an Enumeration trait to select a particular data name ::
729
 
 
730
 
    data_name = Enum("jn0", "jn1", "jn2")
731
 
 
732
 
and a corresponding ``Item`` in the Traits UI View ::
733
 
 
734
 
    Item('data_name', label="Y data")
735
 
 
736
 
By default, an ``Enum`` trait will be displayed as a drop-down. In the
737
 
constructor, we create a dictionary that maps the data names to actual
738
 
numpy arrays: ::
739
 
 
740
 
    # jn is the Bessel function
741
 
    self.data = {“jn0”: jn(0, x),
742
 
                 “jn1”: jn(1, x),
743
 
                 “jn2”: jn(2, x)}
744
 
 
745
 
When we initialize the ArrayPlotData, we’ll set ``y`` to the ``jn0`` array. ::
746
 
 
747
 
    self.plotdata = ArrayPlotData(x = x, y = self.data[“jn0”])
748
 
    plot = Plot(self.plotdata)
749
 
 
750
 
Note that we are storing a reference to the ``plotdata`` object.
751
 
In previous examples, there was no need to keep a reference around (except
752
 
for the one stored inside the Plot object).
753
 
 
754
 
Finally, we create an event handler for the “data_name” Trait. Any time the
755
 
``data_name`` trait changes, we’re going to look it up in the ``self.data``
756
 
dictionary, and push that value into the ``y`` data item in ``ArrayPlotData``. ::
757
 
 
758
 
    def _data_name_changed(self):
759
 
        self.plotdata.set_data("y", self.data[self.data_name])
760
 
 
761
 
Note that there is no actual copying of data here, we’re just passing around
762
 
numpy references.
763
 
 
764
 
The final plot looks like this:
765
 
 
766
 
.. image:: images/data_chooser_example.png
767
 
    :height: 300pt
768
 
 
769
 
 
770
 
.. _connected_plots_example:
771
 
 
772
 
Connected plots
773
 
===============
774
 
 
775
 
One of the features of Chaco’s architecture is that all the underlying
776
 
components of a plot are live objects, connected via events.
777
 
In the next set of examples, we’ll look at how to hook some of those up.
778
 
 
779
 
First, we are going to make two separate plots look at the same data
780
 
space region. This is the full code::
781
 
 
782
 
    class ConnectedRange(HasTraits):
783
 
 
784
 
        container = Instance(HPlotContainer)
785
 
 
786
 
        traits_view = View(Item('container', editor=ComponentEditor(),
787
 
                                show_label=False),
788
 
                           width=1000, height=600, resizable=True,
789
 
                           title="Connected Range")
790
 
 
791
 
        def __init__(self):
792
 
            x = linspace(-14, 14, 100)
793
 
            y = sin(x) * x**3
794
 
            plotdata = ArrayPlotData(x = x, y = y)
795
 
 
796
 
            scatter = Plot(plotdata)
797
 
            scatter.plot(("x", "y"), type="scatter", color="blue")
798
 
 
799
 
            line = Plot(plotdata)
800
 
            line.plot(("x", "y"), type="line", color="blue")
801
 
 
802
 
            self.container = HPlotContainer(scatter, line)
803
 
 
804
 
            scatter.tools.append(PanTool(scatter))
805
 
            scatter.tools.append(ZoomTool(scatter))
806
 
 
807
 
            line.tools.append(PanTool(line))
808
 
            line.tools.append(ZoomTool(line))
809
 
 
810
 
            scatter.range2d = line.range2d
811
 
 
812
 
 
813
 
First, we define a "horizontal" container that displays the plots side
814
 
to side::
815
 
 
816
 
        container = Instance(HPlotContainer)
817
 
 
818
 
        traits_view = View(Item('container', editor=ComponentEditor(),
819
 
                                show_label=False),
820
 
                           width=1000, height=600, resizable=True,
821
 
                           title="Connected Range")
822
 
 
823
 
 
824
 
In the constructor, we define some data and create two plots of it,
825
 
a line plot and a scatter plot, insert them in the container, and add
826
 
pan and zoom tools to both.
827
 
 
828
 
The most important part of the code is the last line of the constructor::
829
 
 
830
 
            scatter.range2d = line.range2d
831
 
 
832
 
Chaco has a concept of *data range* to express bounds in data space.
833
 
There are a series of objects representing this concept.
834
 
The standard 2D plots that we have considered so far all
835
 
have a two-dimensional range on them.
836
 
 
837
 
In this line, we are replacing the range on the scatter plot
838
 
with the range from the line plot. The two plots now share the same
839
 
range object, **and will change together in response to
840
 
changes to the data space bounds**. For example, panning
841
 
or zooming one of the plots
842
 
will result in the same transformation in the other:
843
 
 
844
 
.. image:: images/connected_range_example.png
845
 
    :height: 300pt
846
 
 
847
 
 
848
 
Plot orientation, index and value
849
 
=================================
850
 
 
851
 
We can modify the :ref:`connected plots example <connected_plots_example>`
852
 
such that the two plots only share one of the axes. The 2D data range
853
 
trait is actually composed of two 1D data ranges, and we can access them
854
 
independently. So to link up the x-axes we can substitute the line ::
855
 
 
856
 
            scatter.range2d = line.range2d
857
 
 
858
 
with ::
859
 
 
860
 
            scatter.index_range = line.index_range
861
 
 
862
 
Now the plot can move independently on the y-axis and are link on the x-axis.
863
 
 
864
 
You may have notices that we referred to the x-axis range as *index* range.
865
 
The terms *index* and *value* are quite common in Chaco:
866
 
As it is possible to easily change the orientation of most Chaco plots,
867
 
we want some way to differentiate between the abscissa and the ordinate axes.
868
 
If we just stuck with *x* and *y*, things would get pretty confusing after
869
 
a change in orientation, as one would now, for instance, change the y-axis
870
 
by referring to it as ``x_range``.
871
 
 
872
 
Instead, in Chaco we refer to the data domain as *index*, and to the co-domain
873
 
(the set of possible values) as *value*.
874
 
 
875
 
To illustrate how flexible this concept is, we can switch the orientation
876
 
of the line plot by substituting ::
877
 
 
878
 
            line = Plot(plotdata)
879
 
 
880
 
with ::
881
 
 
882
 
            line = Plot(plotdata, orientation="v", default_origin="top left")
883
 
 
884
 
The ``default_origin`` parameter sets the index axis to be increasing
885
 
downwards. As a result of these changes, now changes to the
886
 
scatter plot index axis (the *x* axis) produces equivalent changes in the
887
 
line plot index axis (the *y* axis):
888
 
 
889
 
.. image:: images/connected_index_example.png
890
 
    :height: 300pt
891
 
 
892
 
 
893
 
Multiple windows
894
 
================
895
 
 
896
 
Chaco components can also be connected beyond the boundary of a single window.
897
 
We will again modify the :ref:`LinePlot example <line_plot_example>`. This
898
 
time, we will create a scatter plot and a line plot with connected ranges
899
 
in different windows.
900
 
 
901
 
First of all, we define a Traits UI view of a customizable plot.
902
 
This is the full code that we will analyze step by step below ::
903
 
 
904
 
    class PlotEditor(HasTraits):
905
 
 
906
 
        plot = Instance(Plot)
907
 
 
908
 
        plot_type = Enum("scatter", "line")
909
 
 
910
 
        orientation = Enum("horizontal", "vertical")
911
 
 
912
 
        traits_view = View(Item('orientation', label="Orientation"),
913
 
                           Item('plot', editor=ComponentEditor(),
914
 
                                show_label=False),
915
 
                           width=500, height=500, resizable=True,
916
 
                           title="Chaco Plot")
917
 
 
918
 
        def __init__(self, *args, **kw):
919
 
            super(PlotEditor, self).__init__(*args, **kw)
920
 
 
921
 
            x = linspace(-14, 14, 100)
922
 
            y = sin(x) * x**3
923
 
            plotdata = ArrayPlotData(x = x, y = y)
924
 
 
925
 
            plot = Plot(plotdata)
926
 
            plot.plot(("x", "y"), type=self.plot_type, color="blue")
927
 
 
928
 
            plot.tools.append(PanTool(plot))
929
 
            plot.tools.append(ZoomTool(plot))
930
 
 
931
 
            self.plot = plot
932
 
 
933
 
        def _orientation_changed(self):
934
 
            if self.orientation == "vertical":
935
 
                self.plot.orientation = "v"
936
 
            else:
937
 
                self.plot.orientation = "h"
938
 
 
939
 
 
940
 
The plot defines two traits, one for the plot type (scatter of line plot) ::
941
 
 
942
 
        plot_type = Enum("scatter", "line")
943
 
 
944
 
and one for the orientation of the plot ::
945
 
 
946
 
        orientation = Enum("horizontal", "vertical")
947
 
 
948
 
The ``plot_type`` trait will not be exposed in the UI, but we add a
949
 
Traits UI item for the orientation: ::
950
 
 
951
 
        traits_view = View(Item('orientation', label="Orientation"), ...)
952
 
 
953
 
Since the ``orientation`` trait is an Enum, this will appear as a drop-down
954
 
box in the window.
955
 
 
956
 
The constructor is very similar to the one used in the previous examples,
957
 
except that we create a new plot of the type specified in the ``plot_type``
958
 
trait: ::
959
 
 
960
 
            plot.plot(("x", "y"), type=self.plot_type, color="blue")
961
 
 
962
 
Finally, we wrote a Trait event handler for the ``orientation`` trait,
963
 
which changes the orientation of the plot as required: ::
964
 
 
965
 
        def _orientation_changed(self):
966
 
            if self.orientation == "vertical":
967
 
                self.plot.orientation = "v"
968
 
            else:
969
 
                self.plot.orientation = "h"
970
 
 
971
 
 
972
 
The :class:`PlotEditor` represents one window. When running the application,
973
 
we can easily create two separate windows, and connect their axes in
974
 
this way: ::
975
 
 
976
 
    if __name__ == "__main__":
977
 
 
978
 
        # create two plots, one of type "scatter", one of type "line"
979
 
        scatter = PlotEditor(plot_type = "scatter")
980
 
        line = PlotEditor(plot_type = "line")
981
 
 
982
 
        # connect the axes of the two plots
983
 
        scatter.plot.range2d = line.plot.range2d
984
 
 
985
 
        # open two windows
986
 
        line.edit_traits()
987
 
        scatter.configure_traits()
988
 
 
989
 
In the last two lines, we open Traits UI editors on both objects.
990
 
Note that we call :meth:`edit_traits()` on the first object,
991
 
and :meth:`configure_traits()` on the second object.
992
 
The technical reason for this is that :meth:`configure_traits()`
993
 
will start the wxPython main loop (thereby blocking the script until the
994
 
window is closed), whereas :meth:`edit_traits()` will not. Thus, when
995
 
opening multiple windows, we would call :meth:`edit_traits()`
996
 
on all but the last one.
997
 
 
998
 
Here is a screenshot of the two windows in action:
999
 
 
1000
 
.. image:: images/connected_windows_example.png
1001
 
    :height: 350pt
1002
 
 
1003
 
 
1004
 
Plot tools: adding interactions
1005
 
===============================
1006
 
 
1007
 
An important feature of Chaco is that it is possible to write re-usable
1008
 
tools to interact directly with the plots.
1009
 
 
1010
 
Chaco takes a modular approach to interactivity. Instead of begin hard-coded
1011
 
into specific plot types or plot renderers,
1012
 
the interaction logic is factored out into classes we call *tools*.
1013
 
An advantage of this approach is that we can add new plot types
1014
 
and container types and still use the old interactions, as long as we
1015
 
adhere to certain basic interfaces.
1016
 
 
1017
 
Thus far, none of the example plots we’ve built are truly interactive,
1018
 
e.g., you cannot pan or zoom them. In the next example, we will modify
1019
 
the :ref:`LinePlot example <line_plot_example>` so that we can pan and zoom. ::
1020
 
 
1021
 
    from chaco.tools.api import PanTool, ZoomTool, DragZoom
1022
 
 
1023
 
    class ToolsExample(HasTraits):
1024
 
 
1025
 
        plot = Instance(Plot)
1026
 
 
1027
 
        traits_view = View(
1028
 
            Item('plot',editor=ComponentEditor(), show_label=False),
1029
 
            width=500, height=500,
1030
 
            resizable=True,
1031
 
            title="Chaco Plot")
1032
 
 
1033
 
        def __init__(self):
1034
 
            x = linspace(-14, 14, 100)
1035
 
            y = sin(x) * x**3
1036
 
            plotdata = ArrayPlotData(x = x, y = y)
1037
 
            plot = Plot(plotdata)
1038
 
            plot.plot(("x", "y"), type="line", color="blue")
1039
 
 
1040
 
            # append tools to pan, zoom, and drag
1041
 
            plot.tools.append(PanTool(plot))
1042
 
            plot.tools.append(ZoomTool(plot))
1043
 
            plot.tools.append(DragZoom(plot, drag_button="right"))
1044
 
 
1045
 
            self.plot = plot
1046
 
 
1047
 
 
1048
 
The example illustrates the general usage pattern: we create a new instance of
1049
 
a Tool, giving it a reference
1050
 
to the Plot, and then we append that tool to a list of tools on the plot.
1051
 
This looks a little redundant, but there is a reason why the tools
1052
 
need a reference back to the plot: the tools use methods and attributes
1053
 
of the plot
1054
 
to transform and interpret the events that it receives, as well as act
1055
 
on those events. Most tools will also modify the attributes on the plot.
1056
 
The pan and zoom tools, for instance, modify the data ranges on the
1057
 
component handed in to it.
1058
 
 
1059
 
Dynamically controlling interactions
1060
 
====================================
1061
 
 
1062
 
One of the nice things about having interactivity bundled up into modular
1063
 
tools is that one can dynamically control when the interaction are allowed
1064
 
and when they are not.
1065
 
 
1066
 
We will modify the previous example so that we can externally control
1067
 
what interactions are available on a plot.
1068
 
 
1069
 
First, we add a new trait to hold a list of names of the tools.
1070
 
This is similar to adding a list of data items
1071
 
in the :ref:`DataChooser example <data_chooser_example>`.
1072
 
However, instead of a drop-down (which is the default editor
1073
 
for an Enumeration trait), we tell Traits that we would like a
1074
 
check list by creating a :class:`CheckListEditor`, so that we will be able
1075
 
to select multiple tools. We give the CheckListEditor a list of possible
1076
 
values, which are just the names of the tools. Notice that these are
1077
 
strings, and not the tool classes themselves.
1078
 
 
1079
 
.. code-block:: python
1080
 
    :linenos:
1081
 
 
1082
 
    from enthought.traits.ui.api import CheckListEditor
1083
 
 
1084
 
    class ToolsExample(HasTraits):
1085
 
 
1086
 
        plot = Instance(Plot)
1087
 
 
1088
 
        tools = List(editor=CheckListEditor(values = ["PanTool",
1089
 
                                     "SimpleZoom", "DragZoom"]))
1090
 
 
1091
 
 
1092
 
In the constructor, we do not add the interactive tools:
1093
 
 
1094
 
.. code-block:: python
1095
 
    :linenos:
1096
 
 
1097
 
        def __init__(self):
1098
 
            x = linspace(-14, 14, 100)
1099
 
            y = sin(x) * x**3
1100
 
            plotdata = ArrayPlotData(x = x, y = y)
1101
 
            plot = Plot(plotdata)
1102
 
            plot.plot(("x", "y"), type="line", color="blue")
1103
 
            self.plot = plot
1104
 
 
1105
 
Instead, we write a trait event handler for the ``tools`` trait:
1106
 
 
1107
 
.. code-block:: python
1108
 
    :linenos:
1109
 
 
1110
 
        def _tools_changed(self):
1111
 
            classes = [eval(class_name) for class_name in self.tools]
1112
 
 
1113
 
            # Remove all tools from the plot
1114
 
            plot_tools = self.plot.tools
1115
 
            for tool in plot_tools:
1116
 
                plot_tools.remove(tool)
1117
 
 
1118
 
            # Create new instances for the selected tool classes
1119
 
            for cls in classes:
1120
 
                self.plot.tools.append(cls(self.plot))
1121
 
 
1122
 
The first line, ::
1123
 
 
1124
 
            classes = [eval(class_name) for class_name in self.tools]
1125
 
 
1126
 
converts the value of the ``tools`` trait (a string) to a Tool class. In the
1127
 
of the method, we remove all the existing tools from the plot ::
1128
 
 
1129
 
            # Remove all tools from the plot
1130
 
            plot_tools = self.plot.tools
1131
 
            for tool in plot_tools:
1132
 
                plot_tools.remove(tool)
1133
 
 
1134
 
and create new ones for the selected items: ::
1135
 
 
1136
 
            # Create new instances for the selected tool classes
1137
 
            for cls in classes:
1138
 
                self.plot.tools.append(cls(self.plot))
1139
 
 
1140
 
 
1141
 
Here is a screenshot of the final result:
1142
 
 
1143
 
.. image:: images/tool_chooser_example.png
1144
 
    :width: 350pt
1145
 
 
1146
 
 
1147
 
Writing a custom tool
1148
 
=====================
1149
 
 
1150
 
It is easy to extend and customize the Chaco framework:
1151
 
the main Chaco components define clear interfaces, so one can write a
1152
 
custom plot or tool, plug it in, and it will play well with the existing
1153
 
pieces.
1154
 
 
1155
 
Our next step is to write a simple, custom tool that will
1156
 
print out the position on the plot under the mouse cursor.
1157
 
This can be done in just a few lines: ::
1158
 
 
1159
 
    from enable.api import BaseTool
1160
 
 
1161
 
    class CustomTool(BaseTool):
1162
 
        def normal_mouse_move(self, event):
1163
 
            print "Screen point:", event.x, event.y
1164
 
 
1165
 
:class:`BaseTool` is an abstract class that forms the interface for tools.
1166
 
It defines a set of methods that are called for the
1167
 
most common mouse and keyboard events. In this case, we define a callback
1168
 
for the ``mouse_move`` event. The prefix ``normal`` indicated the
1169
 
state of the tool, which we will cover next.
1170
 
 
1171
 
All events have an ``x`` and a ``y`` position, and our custom tools is
1172
 
just going to print it out.
1173
 
 
1174
 
.. image:: images/custom_tool_example.png
1175
 
    :height: 250pt
1176
 
 
1177
 
Other event callbacks correspond to mouse gestures (``mouse_enter``,
1178
 
``mouse_leave``, ``mouse_wheel``), mouse clicks (``left_down``, ``left_up``,
1179
 
``right_down``, ``right_up``), and key presses (``key_pressed``).
1180
 
 
1181
 
Stateful tools
1182
 
==============
1183
 
 
1184
 
Chaco tools are stateful. You can think of them as state machines that
1185
 
toggle states based on the events they receive. All tools have at least
1186
 
one state, called "normal". That is why the callback in the previous
1187
 
example began with the prefix ``normal_``.
1188
 
 
1189
 
Our next tool is going to have two states, "normal" and "mousedown".
1190
 
We are going to enter the "mousedown" state when we detect a "left down"
1191
 
event, and we will exit that state when we detect a "left up" event: ::
1192
 
 
1193
 
    class CustomTool(BaseTool):
1194
 
 
1195
 
        event_state = Enum("normal", "mousedown")
1196
 
 
1197
 
        def normal_mouse_move(self, event):
1198
 
            print "Screen:", event.x, event.y
1199
 
 
1200
 
        def normal_left_down(self, event):
1201
 
            self.event_state = "mousedown"
1202
 
            event.handled = True
1203
 
 
1204
 
        def mousedown_left_up(self, event):
1205
 
            self.event_state = "normal"
1206
 
            event.handled = True
1207
 
 
1208
 
Every event has a ``handled`` boolean attribute that can be set to announce
1209
 
that it has been taken care of. Handled events are not propagated further.
1210
 
 
1211
 
So far, the custom tool would stop printing to screen while the left mouse
1212
 
button is pressed. This is because while the tools is in the "mousedown" state,
1213
 
a mouse move event looks for a ``mousedown_mouse_move`` callback method.
1214
 
We can write an implementation for it that maps the screen coordinates in
1215
 
data space:
1216
 
 
1217
 
.. code-block:: python
1218
 
 
1219
 
        def mousedown_mouse_move(self, event):
1220
 
                print "Data:", self.component.map_data((event.x, event.y))
1221
 
 
1222
 
The ``self.component`` attribute contains a reference to the underlying
1223
 
plot. This is why tools need to be given a reference to a plot when
1224
 
they are constructed: almost all tools need to use some capabilities
1225
 
(like ``map_data``) of the components for which they are receiving events.
1226
 
 
1227
 
 
1228
 
.. image:: images/custom_tool_stateful_example.png
1229
 
    :height: 250pt
1230
 
 
1231
 
 
1232
 
Final words
1233
 
===========
1234
 
 
1235
 
This concludes this tutorial. For further information, please refer
1236
 
to the :ref:`Resources` page, or visit the :ref:`User guide`.
1237
 
 
1238
 
 
1239
 
*This tutorial is based on the "Interactive plotting with Chaco" tutorial
1240
 
that was presented by Peter Wang at Scipy 2008*