source: orange/docs/extend-widgets/rst/basics.rst @ 11408:c2d2400b6a90

Revision 11408:c2d2400b6a90, 13.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Fixes for Widgets Development documentation.

2Getting Started
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.
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.
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.
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:
37.. image:: widgettoolbox.png
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:
49.. image:: explorer.png
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
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>
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.
78Widgets communicate. They use typed channels, and exchange
79tokens. Each widget would define its input and output channels in
80something like::
82    self.inputs = [("Test Data Set", ExampleTable, self.cdata), ("Learner", orange.Learner, self.learner, 0)]
83    self.outputs = [("Evaluation Results", orngTest.ExperimentResults)]
85Above two lines are for Test Learners widget, so hovering with your
86mouse over its icon in the widget toolbox would yield:
88.. image:: mouseoverwidgetintoolbox.png
90We will go over the syntax of channel definitions later, but for
91now the following is important:
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.
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.
104Let's Start
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 ``: 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.
116The script defining the OWDataSamplerA widget starts with a follwing header::
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>
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).
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::
132    from OWWidget import *
133    import OWGUI
135    class OWDataSamplerA(OWWidget):
137        def __init__(self, parent=None, signalManager=None):
138            OWWidget.__init__(self, parent, signalManager, 'SampleDataA')
140            self.inputs = [("Data", ExampleTable,]
141            self.outputs = [("Sampled Data", ExampleTable)]
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)
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
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.
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.
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
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)::
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 =, 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)
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.
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".
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.
226   Although our widget is now ready to test, for a final touch, let's
227   design an icon for our widget. As specified in the widget header, we
228   will call it :download:`DataSamplerA.png <DataSamplerA.png>` and will
229   put it in icons subdirectory of OrangeWidgets directory (together with
230   all other icons of other widgets).
232For a test, we now open Orange Canvas. There should be a new pane in a
233widget toolbox called Test (this is the name of the directory we have
234used to put in our widget). If we click on this pane, it displays an
235icon of our widget. Try to hoover on it to see if the header and
236channel info was processed correctly:
238.. image:: samplewidgetontoolbox.png
240Now for the real test. We put the File widget on the schema (from
241Data pane), read data set. We also put our Data Sampler widget on the pane and
242open it (double click on the icon, or right-click and choose
245.. image:: datasamplerAempty.png
247Drag this window off the window with the widget schema of Orange
248Canvas, and connect File and Data Sampler widget (click on an ouput
249connector - green box - of the File widget, and drag the line to the
250input connector of the Data Sampler). If everything is ok, as soon as
251you release the mouse the connection is established and, the token
252that was waiting on the output of the file widget was sent to the Data
253Sampler widget, which in turn updated its window:
255.. image:: datasamplerAupdated.png
257To see if the Data Sampler indeed sent some data to the output,
258connect it to the Data Table widget:
260.. image:: schemawithdatatable.png
262Try opening different data files (the change should propagate
263through your widgets and with Data Table window open, you should
264immediately see the result of sampling). Try also removing the
265connection between File and Data Sampler (right click on the
266connection, choose Remove). What happens to the data displayed in the
267Data Table?
270Testing Your Widget Outside Orange Canvas
273When prototyping a single widget, for a fast test I often get
274bored of running Orange Canvas, setting the schema and clicking on
275icons to get widget windows. There are two options to bypass this. The
276first one is to add a testing script at the end of your widget. To do
277this, we finished Data Sampler with::
279    if __name__=="__main__":
280        appl = QApplication(sys.argv)
281        ow = OWDataSamplerA()
283        dataset = orange.ExampleTable('')
285        appl.exec_()
287These are essentially some calls to Qt routines that run GUI for our
288widgets. At the core, however, notice that instead of sending the
289token to the input channel, we directly called the routine for token
290handling (:obj:`data`).
292To test your widget in more complex environment, that for instance
293requires to set a complex schema in which your widget collaborates,
294use Orange Canvas to set the schema and then either 1) save the schema
295to be opened every time you run Orange Canvas, or 2) save this schema
296(File menu) as an application within a single file you will need to
297run each time you will test your widget.
Note: See TracBrowser for help on using the repository browser.