r11439 r11593 14 14 some really nice interaction may be over 1000 lines long. 15 15 16 When we have started to write this tutorial, we have been working 17 on widgets for quite a while. There are now (now being in the very 18 time this page has been crafted) about 50 widgets available, and we 19 have pretty much defined how widgets and their interfaces should look 20 like. We have also made some libraries that help set up GUI with only 21 a few lines of code, and some mechanisms that one may found useful and 22 user friendly, like progress bars and alike. 23 24 16 On this page, we will start with some simple essentials, and then 25 17 show how to build a simple widget that will be ready to run within … … 33 25 category has an associated priority. Opening Orange Canvas, a visual 34 26 programming environment that comes with Orange, widgets are listed in 35 toolbox on the top of the window: 27 : 36 28 37 29 .. image:: widgettoolbox.png 38 30 39 By default, Orange is installed in site-packages directory of 40 Python libraries. Widgets are all put in the subdirectories of 41 OrangeWidget directory; these subdirectories define widget 42 categories. For instance, under windows and default settings, a 43 directory that stores all the widgets displayed in the Evaluate pane is 44 *C:\\Python23\\Lib\\site-packages\\Orange\\OrangeWidgets\\Evaluate*. Figure 45 above shows that at the time of writing of this text there were five 46 widgets for evaluation of classifiers, and this is how my Evaluate 47 directory looked like: 48 49 .. image:: explorer.png 50 51 Notice that there are a number of files in Evaluate directory, so 52 how does Orange Canvas distinguish those that define widgets? Well, 53 widgets are Python script files that start with a header. Here is a 54 header for OWTestLearners.py:: 55 31 The widgets and categories to which they belong are discovered at Orange 32 Canvas startup leveraging setuptools/distribute and it's `entry points 33 <http://pythonhosted.org/distribute/setuptools.html#dynamic-discovery-of-services-and-plugins>`_ 34 protocol. In particular Orange Canvas looks for widgets using a 35 `orange.widgets` entry point. 36 37 38 First we will examine an existing widget in Orange. The Test Learners 39 widget which is implemented in `OWTestLearners.py 40 <http://orange.biolab.si/trac/browser/orange/Orange/OrangeWidgets/Evaluate/OWTestLearners.py>`_. 41 42 Here is its header:: 43 44 """ 56 45 <name>Test Learners</name> 57 46 <description>Estimates the predictive performance of learners on a data set.</description> 58 <icon>icons/TestLearners .png</icon> 47 <icon>icons/TestLearnersg</icon> 59 48 <priority>200</priority> 60 61 OWTestLearners is a Python script, so the header information we 49 """ 50 51 OWTestLearners is a Python module, so the header information we 62 52 show about lies within the comment block, with triple quote opening 63 53 and closing the comment. Header defines the name of the widget, its … … 65 55 icon, and a number expressing the priority of the widget. The name of 66 56 the widget as given in the header will be the one that will be used 67 throughout in Orange Canvas. As for naming, the actual file name of 68 the widget is not important. The description of the widget is shown 69 once mouse rests on an toolbar icon representing the widget. And for 57 throughout in Orange Canvas. The description of the widget is shown 58 once mouse rests on an toolbox icon representing the widget. And for 70 59 the priority: this determines the order in which widgets appear in the 71 toolbox. The one shown above for Evaluate groups has widget named Test 72 Learners with priority 200, Classifications with 300, ROC Analysis 73 with 1010, Lift Curve with 1020 and Calibration Plot with 1030. Notice 74 that every time the priority number crosses a multiplier of a 1000, 75 there is a gap in the toolbox between the widgets; in this way, a 76 subgroups of the widgets within the same group can be imposed. 60 toolbox within a category. 77 61 78 62 Widgets communicate. They use typed channels, and exchange … … 84 68 self.outputs = [("Evaluation Results", orngTest.ExperimentResults)] 85 69 86 Above two lines are for Test Learners widget, so hovering with your 87 mouse over its icon in the widget toolbox would yield: 88 89 .. image:: mouseoverwidgetintoolbox.png 90 70 91 71 We will go over the syntax of channel definitions later, but for … … 93 73 94 74 - Widgets are defined in a Python files. 95 - For Orange and Orange canvas to find them, they reside in subdirectories 96 in OrangeWidgets directory of Orange installation. The name of the 97 subdirectory matters, as this is the name of the widget category. Widgets 98 in the same directory will be grouped in the same pane of widget toolbox 99 in Orange Canvas. 100 - A file describing a widget starts with a header. This, given in sort of 101 XMLish style, tells about the name, short description, location of an 102 icon and priority of the widget. 75 - Widgets are registered through entry points and are discovered at 76 runtime. 77 - A python module implementing a widget starts with a header. This, given 78 in sort of XMLish style, tells about the name, short description, 79 location of an icon and priority of the widget. 103 80 - The sole role of priority is to specify the placement (order) of widgets 104 81 in the Orange Canvas toolbox. … … 108 85 is seen from the outside. 109 86 110 Oh, by the way. Orange caches widget descriptions to achieve a faster 111 startup, but this cache is automatically refreshed at startup if any change 112 is detected in widgets' files. 87 .. note:: 88 Orange caches widget descriptions to achieve a faster startup, 89 but this cache is automatically refreshed at startup if any change 90 is detected in widgets' file. 113 91 114 92 *********** … … 119 97 have some fun and write a widget. We will start with a very simple 120 98 one, that will receive a data set on the input and will output a data 121 set with 10% of the data instances. Not to mess with other widgets, we 122 will create a Test directory within OrangeWidgets directory, and write 123 the widget in a file called `OWDataSamplerA.py`: OW for Orange Widget, 124 DataSampler since this is what widget will be doing, and A since we 125 prototype a number of this widgets in our tutorial. 126 127 The script defining the OWDataSamplerA widget starts with a follwing header:: 99 set with 10% of the data instances. We will call this widget 100 `OWDataSamplerA.py` (OW for Orange Widget, DataSampler since this is what 101 widget will be doing, and A since we prototype a number of this widgets 102 in our tutorial). 103 104 But first we must create a simple `python project`_ layout called *Demo*, 105 that we will use in the rest of this tutorial. 106 107 .. _`python project`: http://docs.python.org/2/distutils/examples.html#pure-python-distribution-by-package 108 109 The layout should be:: 110 111 Demo/ 112 setup.py 113 orangedemo/ 114 __init__.py 115 OWDataSamplerA.py 116 117 and the :download:`setup.py` should contain 118 119 .. literalinclude:: setup.py 120 121 Note that we declare our *orangedemo* package as containing widgets 122 from an ad hoc defined category *Demo*. 123 124 Following the previous example of OWTestLearners, our module defining 125 the OWDataSamplerA widget starts with a following header:: 128 126 129 127 <name>Data Sampler</name> 130 128 <description>Randomly selects a subset of instances from the data set</description> 131 <icon>icons/DataSamplerA. png</icon> 129 <icon>icons/DataSamplerA.g</icon> 132 130 <priority>10</priority> 133 131 … … 138 136 139 137 Orange Widgets are all derived from the class OWWidget. The name of 140 the class should bematch the file name, so the lines following the 138 the class should match the file name, so the lines following the 141 139 header in our Data Sampler widget should look something like:: 142 140 141 143 142 from OWWidget import * 144 143 import OWGUI … … 147 146 148 147 def __init__(self, parent=None, signalManager=None): 149 OWWidget.__init__(self, parent, signalManager , 'SampleDataA') 150 151 self.inputs = [("Data", ExampleTable, self.data)] 152 self.outputs = [("Sampled Data", ExampleTable)] 148 OWWidget.__init__(self, parent, signalManager) 149 150 self.inputs = [("Data", Table, self.data)] 151 self.outputs = [("Sampled Data", Table)] 153 152 154 153 # GUI … … 158 157 self.resize(100,50) 159 158 160 In initialization, the widget calls the :obj:`__init__` function 161 of a base class, passing the name 'SampleData' which will, 162 essentially, be used for nothing else than a stem of a file for saving 163 the parameters of the widgets (we will regress on these somehow 164 latter in tutorial). Widget then defines inputs and outputs. For 165 input, widget defines a "Data" channel, accepting tokens of the type 166 orange.ExampleTable and specifying that :obj:`data` function will 159 In initialization, the widget calls the :func:`__init__` method 160 of a base class. Widget then defines inputs and outputs. For input, 161 this is a *Data* channel, accepting tokens of the type 162 :class:`Orange.data.Table` and specifying that :func:`data` method will 167 163 be used to handle them. For now, we will use a single output channel 168 164 called "Sampled Data", which will be of the same type 169 ( orange.ExampleTable). 170 171 Notice that the types of the channels are 172 specified by a class name; you can use any classes here, but if your 173 widgets need to talk with other widgets in Orange, you will need to 174 check which classes are used there. Luckily, and as one of the main 175 design principles, there are just a few channel types that current 176 Orange widgets areusing. 165 (Table). 166 167 Notice that the types of the channels are 168 you can use any class here, but if your widgets need to talk with 169 other widgets in Orange, you will need to check which classes are 170 used there. Luckily, and as one of the main design principles, 171 there are just a few channel types that current Orange widgets are 172 using. 177 173 178 174 The next four lines specify the GUI of our widget. This will be … … 196 192 197 193 In order to complete our widget, we now need to define how will it 198 handle the input data. This is done in a function called 199 :obj:`data` (remember, we did introduce this name inthe 200 specification of theinput channel):: 194 handle the input data. This is done in a 195 the 196 input channel):: 201 197 202 198 def data(self, dataset): … … 213 209 self.send("Sampled Data", None) 214 210 215 The function is defined within a class definition, so its first 216 argument has to be :obj:`self`. The second argument called 217 :obj:`dataset` is the token sent through the input channel which 218 our function needs to handle. 211 The :obj:`dataset` argument is the token sent through the input 212 channel which our method needs to handle. 219 213 220 214 To handle the non-empty token, the widget updates the interface … … 225 219 "Sampled Data". 226 220 227 Notice that the token can be empty (``dataset is None``), 228 resulting from either the sending widget to which we have connected 229 intentionally emptying the channel, or when the link between the two 230 widgets is removed. In any case, it is important that we always write 231 token handlers that appropriately handle the empty tokens. In our 232 implementation, we took care of empty input data set by appropriately 233 setting the GUI of a widget and sending an empty token to the 234 output channel. 235 236 .. 237 Although our widget is now ready to test, for a final touch, let's 238 design an icon for our widget. As specified in the widget header, we 239 will call it :download:`DataSamplerA.png <DataSamplerA.png>` and will 240 put it in icons subdirectory of OrangeWidgets directory (together with 241 all other icons of other widgets). 221 Notice that the token can be empty (``None``), resulting from either 222 the sending widget to which we have connected intentionally emptying 223 the channel, or when the link between the two widgets is removed. 224 In any case, it is important that we always write token handlers 225 that appropriately handle the empty tokens. In our implementation, 226 we took care of empty input data set by appropriately setting the 227 GUI of a widget and sending an empty token to the output channel. 228 229 230 Although our widget is now ready to test, for a final touch, let's 231 design an icon for our widget. As specified in the widget header, we 232 will call it :download:`DataSamplerA.svg <DataSamplerA.svg>` and will 233 put it in `icons` subdirectory of `orangedemo` directory. 234 235 With this we cen now go ahead and install the orangedemo package. We 236 will do this by running :code:`python setup.py develop` command from 237 the `Demo` directory. 238 239 .. note:: 240 Depending on your python installation you might need 241 administrator/superuser privileges. 242 242 243 243 For a test, we now open Orange Canvas. There should be a new pane in a 244 widget toolbox called Test (this is the name of the directory we have 245 used to put in our widget). If we click on this pane, it displays an 246 icon of our widget. Try to hoover on it to see if the header and 247 channel info was processed correctly: 244 widget toolbox called Demo. If we click on this pane, it displays an 245 icon of our widget. Try to hover on it to see if the header and channel 246 info was processed correctly: 248 247 249 248 .. image:: samplewidgetontoolbox.png 250 249 251 250 Now for the real test. We put the File widget on the schema (from 252 Data pane) , read iris.tab data set. We also put our Data Sampler widget on the pane and 253 open it (double click on the icon, or right-click and choose 254 Open): 251 Data pane) 252 Sampler widget on the scheme and open it (double click on the icon, 253 Open): 255 254 256 255 .. image:: datasamplerAempty.png 257 256 258 Drag this window off the window with the widget schema of Orange 259 Canvas, and connect File and Data Sampler widget (click on an ouput 260 connector - green box - of the File widget, and drag the line to the 261 input connector of the Data Sampler). If everything is ok, as soon as 262 you release the mouse the connection is established and, the token 263 that was waiting on the output of the file widget was sent to the Data 264 Sampler widget, which in turn updated its window: 257 Now connect the File and Data Sampler widget (click on an output 258 connector of the File widget, and drag the line to the input connector 259 of the Data Sampler). If everything is ok, as soon as you release the 260 mouse, the connection is established and, the token that was waiting 261 on the output of the file widget was sent to the Data Sampler widget, 262 which in turn updated its window: 265 263 266 264 .. image:: datasamplerAupdated.png … … 292 290 ow = OWDataSamplerA() 293 291 ow.show() 294 dataset = orange.ExampleTable('iris.tab') 292 dataset = Table('iris.tab') 295 293 ow.data(dataset) 296 294 appl.exec_() 297 295 298 296 These are essentially some calls to Qt routines that run GUI for our 299 widgets. At the core, however, notice that instead of sending the 300 token to the input channel, we directly called the routine for token 301 handling (:obj:`data`). 302 303 To test your widget in more complex environment, that for instance 304 requires to set a complex schema in which your widget collaborates, 305 use Orange Canvas to set the schema and then either 1) save the schema 306 to be opened every time you run Orange Canvas, or 2) save this schema 307 (File menu) as an application within a single file you will need to 308 run each time you will test your widget. 297 widgets. Notice that we call the :func:`data` method directly.
Note: See TracChangeset for help on using the changeset viewer.