source: orange/docs/extend-widgets/rst/basics.rst @ 11050:e3c4699ca155

Revision 11050:e3c4699ca155, 13.9 KB checked in by Miha Stajdohar <miha.stajdohar@…>, 16 months ago (diff)

Widget docs From HTML to Sphinx.

Line 
1###############
2Getting Started
3###############
4
5
6The tutorial on these pages is meant for those who are interested in
7developing widgets in Orange. Orange Widgets are components in
8Orange's visual programming environment. They are wrappers around some
9data analysis code that provide graphical user interface
10(GUI). Widgets communicate, and pass tokens through communication
11channels to interact with other widgets. While simplest widgets
12consist of even less than 100 lines of code, those more complex that
13often implement some fancy graphical display of data and allow for
14some really nice interaction may be over 1000 lines long.
15
16When we have started to write this tutorial, we have been working
17on widgets for quite a while. There are now (now being in the very
18time this page has been crafted) about 50 widgets available, and we
19have pretty much defined how widgets and their interfaces should look
20like. We have also made some libraries that help set up GUI with only
21a few lines of code, and some mechanisms that one may found useful and
22user friendly, like progress bars and alike.
23
24On this page, we will start with some simple essentials, and then
25show how to build a simple widget that will be ready to run within
26Orange Canvas, our visual programming environment.
27
28*************
29Prerequisites
30*************
31
32Each Orange widget belongs to a category and within a
33category has an associated priority. Opening Orange Canvas, a visual
34programming environment that comes with Orange, widgets are listed in
35toolbox on the top of the window:
36
37.. image:: widgettoolbox.png
38
39By default, Orange is installed in site-packages directory of
40Python libraries. Widgets are all put in the subdirectories of
41OrangeWidget directory; these subdirectories define widget
42categories. For instance, under windows and default settings, a
43directory that stores all the widgets displayed in the Evaluate pane is
44C:\Python23\Lib\site-packages\orange\OrangeWidgets\Evaluate. Figure
45above shows that at the time of writing of this text there were five
46widgets for evaluation of classifiers, and this is how my Evaluate
47directory looked like:
48
49.. image:: explorer.png
50
51Notice that there are a number of files in Evaluate directory, so
52how does Orange Canvas distinguish those that define widgets? Well,
53widgets are Python script files that start with a header. Here is a
54header for OWTestLearners.py::
55
56    <name>Test Learners</name>
57    <description>Estimates the predictive performance of learners on a data set.</description>
58    <icon>icons/TestLearners.png</icon>
59    <priority>200</priority>
60
61OWTestLearners is a Python script, so the header information we
62show about lies within the comment block, with triple quote opening
63and closing the comment. Header defines the name of the widget, its
64description, the name of the picture file the widget will use for an
65icon, and a number expressing the priority of the widget. The name of
66the widget as given in the header will be the one that will be used
67throughout in Orange Canvas. As for naming, the actual file name of
68the widget is not important. The description of the widget is shown
69once mouse rests on an toolbar icon representing the widget. And for
70the priority: this determines the order in which widgets appear in the
71toolbox. The one shown above for Evaluate groups has widget named Test
72Learners with priority 200, Classifications with 300, ROC Analysis
73with 1010, Lift Curve with 1020 and Calibration Plot with 1030. Notice
74that every time the priority number crosses a multiplier of a 1000,
75there is a gap in the toolbox between the widgets; in this way, a
76subgroups of the widgets within the same group can be imposed.
77
78Widgets communicate. They use typed channels, and exchange
79tokens. Each widget would define its input and output channels in
80something like::
81
82    self.inputs = [("Test Data Set", ExampleTable, self.cdata), ("Learner", orange.Learner, self.learner, 0)]
83    self.outputs = [("Evaluation Results", orngTest.ExperimentResults)]
84
85Above two lines are for Test Learners widget, so hovering with your
86mouse over its icon in the widget toolbox would yield:
87
88.. image:: mouseoverwidgetintoolbox.png
89
90We will go over the syntax of channel definitions later, but for
91now the following is important:
92
93   -  Widgets are defined in a Python files.
94   - For Orange and Orange canvas to find them, they reside in subdirectories in OrangeWidgets directory of Orange installation. The name of the subdirectory matters, as this is the name of the widget category. Widgets in the same directory will be grouped in the same pane of widget toolbox in Orange Canvas.
95   - A file describing a widget starts with a header. This, given in sort of XMLish style, tells about the name, short description, location of an icon and priority of the widget.
96   - The sole role of priority is to specify the placement (order) of widgets in the Orange Canvas toolbox.
97   - Somewhere in the code (where we will learn later) there are two lines which tell which channels the widgets uses for communication. These, together with the header information, completely specify the widget as it is seen from the outside.
98
99Oh, by the way. Orange caches widget descriptions to achieve a faster
100startup, but this cache is automatically refreshed at startup if any change
101is detected in widgets' files.
102
103***********
104Let's Start
105***********
106
107Now that we went through some of the more boring stuff, let us now
108have some fun and write a widget. We will start with a very simple
109one, that will receive a data set on the input and will output a data
110set with 10% of the data instances. Not to mess with other widgets, we
111will create a Test directory within OrangeWidgets directory, and write
112the widget in a file called `OWDataSamplerA <OWDataSamplerA.py>`: OW for Orange Widget,
113DataSampler since this is what widget will be doing, and A since we
114prototype a number of this widgets in our tutorial.
115
116The script defining the OWDataSamplerA widget starts with a follwing header::
117
118    <name>Data Sampler</name>
119    <description>Randomly selects a subset of instances from the data set</description>
120    <icon>icons/DataSamplerA.png</icon>
121    <priority>10</priority>
122
123This should all be clear now, perhaps just a remark on an icon. We
124can put any name here, and if Orange Canvas won't find the
125corresponding file, it will use a file called Unknown.png (an icon
126with a question mark).
127
128Orange Widgets are all derived from the class OWWidget. The name of
129the class should be match the file name, so the lines following the
130header in our Data Sampler widget should look something like::
131
132    from OWWidget import *
133    import OWGUI
134
135    class OWDataSamplerA(OWWidget):
136
137        def __init__(self, parent=None, signalManager=None):
138            OWWidget.__init__(self, parent, signalManager, 'SampleDataA')
139
140            self.inputs = [("Data", ExampleTable, self.data)]
141            self.outputs = [("Sampled Data", ExampleTable)]
142
143            # GUI
144            box = OWGUI.widgetBox(self.controlArea, "Info")
145            self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
146            self.infob = OWGUI.widgetLabel(box, '')
147            self.resize(100,50)
148
149In initialization, the widget calls the :obj:`init` function
150of a base class, passing the name 'SampleData' which will,
151essentially, be used for nothing else than a stem of a file for saving
152the parameters of the widgets (we will regress on these somehow
153latter in tutorial). Widget then defines inputs and outputs. For
154input, widget defines a "Data" channel, accepting tokens of the type
155orange.ExampleTable and specifying that :obj:`data` function will
156be used to handle them. For now, we will use a single output channel
157called "Sampled Data", which will be of the same type
158(orange.ExampleTable).
159
160Notice that the types of the channels are
161specified by a class name; you can use any classes here, but if your
162widgets need to talk with other widgets in Orange, you will need to
163check which classes are used there. Luckily, and as one of the main
164design principles, there are just a few channel types that current
165Orange widgets are using.
166
167The next four lines specify the GUI of our widget. This will be
168simple, and will include only two lines of text of which, if nothing
169will happen, the first line will report on "no data yet", and second
170line will be empty. By (another) design principles, in an interface
171Orange widgets are most often split to control and main area. Control
172area appears on the left and should include any controls for settings
173or options that your widget will use. Main are would most often
174include a graph, table or some drawing that will be based on the
175inputs to the widget and current options/setting in the control
176area. OWWidget make these two areas available through its attributes
177:obj:`self.controlArea` and :obj:`self.mainArea`. Notice
178that while it would be nice for all widgets to have this common visual
179look, you can use these areas in any way you want to, even disregarding one
180and composing your widget completely unlike the others in Orange.
181
182As our widget won't display anything apart from some info, we will
183place the two labels in the control area and surround it with the box
184"Info".
185
186In order to complete our widget, we now need to define how will it
187handle the input data. This is done in a function called
188:obj:`data` (remember, we did introduce this name in the
189specification of the input channel)::
190
191    def data(self, dataset):
192        if dataset:
193            self.infoa.setText('%d instances in input data set' % len(dataset))
194            indices = orange.MakeRandomIndices2(p0=0.1)
195            ind = indices(dataset)
196            sample = dataset.select(ind, 0)
197            self.infob.setText('%d sampled instances' % len(sample))
198            self.send("Sampled Data", sample)
199        else:
200            self.infoa.setText('No data on input yet, waiting to get something.')
201            self.infob.setText('')
202            self.send("Sampled Data", None)
203
204The function is defined within a class definition, so its first
205argument has to be :obj:`self`. The second argument called
206:obj:`dataset` is the token sent through the input channel which
207our function needs to handle.
208
209To handle the non-empty token, the widget updates the interface
210reporting on number of data items on the input, then does the data
211sampling using Orange's routines for these, and updates the
212interface reporting on the number of sampled instances. Finally, the
213sampled data is sent as a token to the output channel with a name
214"Sampled Data".
215
216Notice that the token can be empty (:obj:`dataset==None`),
217resulting from either the sending widget to which we have connected
218intentionally emptying the channel, or when the link between the two
219widgets is removed. In any case, it is important that we always write
220token handlers that appropriately handle the empty tokens. In our
221implementation, we took care of empty input data set by appropriately
222setting the GUI of a widget and sending an empty token to the
223output channel.
224
225Although our widget is now ready to test, for a final touch, let's
226design an icon for our widget. As specified in the widget header, we
227will call it `DataSamplerA.png <DataSamplerA.png>`_ and will
228put it in icons subdirectory of OrangeWidgets directory (together with
229all other icons of other widgets).
230
231For a test, we now open Orange Canvas. There should be a new pane in a
232widget toolbox called Test (this is the name of the directory we have
233used to put in our widget). If we click on this pane, it displays an
234icon of our widget. Try to hoover on it to see if the header and
235channel info was processed correctly:
236
237.. image:: samplewidgetontoolbox.png
238
239Now for the real test. We put the File widget on the schema (from
240Data pane), read iris.tab data set. We also put our Data Sampler widget on the pane and
241open it (double click on the icon, or right-click and choose
242Open):
243
244.. image:: datasamplerAempty.png
245
246Drag this window off the window with the widget schema of Orange
247Canvas, and connect File and Data Sampler widget (click on an ouput
248connector - green box - of the File widget, and drag the line to the
249input connector of the Data Sampler). If everything is ok, as soon as
250you release the mouse the connection is established and, the token
251that was waiting on the output of the file widget was sent to the Data
252Sampler widget, which in turn updated its window:
253
254.. image:: datasamplerAupdated.png
255
256To see if the Data Sampler indeed sent some data to the output,
257connect it to the Data Table widget:
258
259.. image:: schemawithdatatable.png
260
261Try opening different data files (the change should propagate
262through your widgets and with Data Table window open, you should
263immediately see the result of sampling). Try also removing the
264connection between File and Data Sampler (right click on the
265connection, choose Remove). What happens to the data displayed in the
266Data Table?
267
268*****************************************
269Testing Your Widget Outside Orange Canvas
270*****************************************
271
272When prototyping a single widget, for a fast test I often get
273bored of running Orange Canvas, setting the schema and clicking on
274icons to get widget windows. There are two options to bypass this. The
275first one is to add a testing script at the end of your widget. To do
276this, we finished Data Sampler with::
277
278    if __name__=="__main__":
279        appl = QApplication(sys.argv)
280        ow = OWDataSamplerA()
281        ow.show()
282        dataset = orange.ExampleTable('iris.tab')
283        ow.data(dataset)
284        appl.exec_()
285
286These are essentially some calls to Qt routines that run GUI for our
287widgets. At the core, however, notice that instead of sending the
288token to the input channel, we directly called the routine for token
289handling (:obj:`data`).
290
291To test your widget in more complex environment, that for instance
292requires to set a complex schema in which your widget collaborates,
293use Orange Canvas to set the schema and then either 1) save the schema
294to be opened every time you run Orange Canvas, or 2) save this schema
295(File menu) as an application within a single file you will need to
296run each time you will test your widget.
Note: See TracBrowser for help on using the repository browser.