source: orange/orange/doc/extend-widgets/basics.htm @ 9398:a6b3d9c13ee0

Revision 9398:a6b3d9c13ee0, 14.4 KB checked in by mitar, 2 years ago (diff)

Renaming documentation for widgets developers.

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