Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • docs/extend-widgets/rst/basics.rst

    r11439 r11593  
    1414some really nice interaction may be over 1000 lines long. 
    1515 
    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  
    2416On this page, we will start with some simple essentials, and then 
    2517show how to build a simple widget that will be ready to run within 
     
    3325category has an associated priority. Opening Orange Canvas, a visual 
    3426programming environment that comes with Orange, widgets are listed in 
    35 toolbox on the top of the window: 
     27a toolbox on the left: 
    3628 
    3729.. image:: widgettoolbox.png 
    3830 
    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  
     31The widgets and categories to which they belong are discovered at Orange 
     32Canvas startup leveraging setuptools/distribute and it's `entry points 
     33<http://pythonhosted.org/distribute/setuptools.html#dynamic-discovery-of-services-and-plugins>`_ 
     34protocol. In particular Orange Canvas looks for widgets using a 
     35`orange.widgets` entry point. 
     36 
     37 
     38First we will examine an existing widget in Orange. The Test Learners 
     39widget which is implemented in `OWTestLearners.py 
     40<http://orange.biolab.si/trac/browser/orange/Orange/OrangeWidgets/Evaluate/OWTestLearners.py>`_. 
     41 
     42Here is its header:: 
     43 
     44    """ 
    5645    <name>Test Learners</name> 
    5746    <description>Estimates the predictive performance of learners on a data set.</description> 
    58     <icon>icons/TestLearners.png</icon> 
     47    <icon>icons/TestLearners1.svg</icon> 
    5948    <priority>200</priority> 
    60  
    61 OWTestLearners is a Python script, so the header information we 
     49    """ 
     50 
     51OWTestLearners is a Python module, so the header information we 
    6252show about lies within the comment block, with triple quote opening 
    6353and closing the comment. Header defines the name of the widget, its 
     
    6555icon, and a number expressing the priority of the widget. The name of 
    6656the 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 
     57throughout in Orange Canvas. The description of the widget is shown 
     58once mouse rests on an toolbox icon representing the widget. And for 
    7059the 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. 
     60toolbox within a category. 
    7761 
    7862Widgets communicate. They use typed channels, and exchange 
     
    8468    self.outputs = [("Evaluation Results", orngTest.ExperimentResults)] 
    8569 
    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 
    9070 
    9171We will go over the syntax of channel definitions later, but for 
     
    9373 
    9474   - 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. 
    10380   - The sole role of priority is to specify the placement (order) of widgets 
    10481     in the Orange Canvas toolbox. 
     
    10885     is seen from the outside. 
    10986 
    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. 
    11391 
    11492*********** 
     
    11997have some fun and write a widget. We will start with a very simple 
    12098one, 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:: 
     99set with 10% of the data instances. We will call this widget 
     100`OWDataSamplerA.py` (OW for Orange Widget, DataSampler since this is what 
     101widget will be doing, and A since we prototype a number of this widgets 
     102in our tutorial). 
     103 
     104But first we must create a simple `python project`_ layout called *Demo*, 
     105that 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 
     109The layout should be:: 
     110 
     111   Demo/ 
     112         setup.py 
     113         orangedemo/ 
     114                     __init__.py 
     115                     OWDataSamplerA.py 
     116 
     117and the :download:`setup.py` should contain 
     118 
     119.. literalinclude:: setup.py 
     120 
     121Note that we declare our *orangedemo* package as containing widgets 
     122from an ad hoc defined category *Demo*. 
     123 
     124Following the previous example of OWTestLearners, our module defining 
     125the OWDataSamplerA widget starts with a following header:: 
    128126 
    129127    <name>Data Sampler</name> 
    130128    <description>Randomly selects a subset of instances from the data set</description> 
    131     <icon>icons/DataSamplerA.png</icon> 
     129    <icon>icons/DataSamplerA.svg</icon> 
    132130    <priority>10</priority> 
    133131 
     
    138136 
    139137Orange Widgets are all derived from the class OWWidget. The name of 
    140 the class should be match the file name, so the lines following the 
     138the class should match the file name, so the lines following the 
    141139header in our Data Sampler widget should look something like:: 
    142140 
     141    import Orange 
    143142    from OWWidget import * 
    144143    import OWGUI 
     
    147146 
    148147        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", Orange.data.Table, self.data)] 
     151            self.outputs = [("Sampled Data", Orange.data.Table)] 
    153152 
    154153            # GUI 
     
    158157            self.resize(100,50) 
    159158 
    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 
     159In initialization, the widget calls the :func:`__init__` method 
     160of a base class. Widget then defines inputs and outputs. For input, 
     161this is a *Data* channel, accepting tokens of the type 
     162:class:`Orange.data.Table` and specifying that :func:`data` method will 
    167163be used to handle them. For now, we will use a single output channel 
    168164called "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 are using. 
     165(Orange.data.Table). 
     166 
     167Notice that the types of the channels are specified by a class; 
     168you can use any class here, but if your widgets need to talk with 
     169other widgets in Orange, you will need to check which classes are 
     170used there. Luckily, and as one of the main design principles, 
     171there are just a few channel types that current Orange widgets are 
     172using. 
    177173 
    178174The next four lines specify the GUI of our widget. This will be 
     
    196192 
    197193In 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 in the 
    200 specification of the input channel):: 
     194handle the input data. This is done in a method called :func:`data` 
     195(remember, we did introduce this name in the specification of the 
     196input channel):: 
    201197 
    202198    def data(self, dataset): 
     
    213209            self.send("Sampled Data", None) 
    214210 
    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. 
     211The :obj:`dataset` argument is the token sent through the input 
     212channel which our method needs to handle. 
    219213 
    220214To handle the non-empty token, the widget updates the interface 
     
    225219"Sampled Data". 
    226220 
    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). 
     221Notice that the token can be empty (``None``), resulting from either 
     222the sending widget to which we have connected intentionally emptying 
     223the channel, or when the link between the two widgets is removed. 
     224In any case, it is important that we always write token handlers 
     225that appropriately handle the empty tokens. In our implementation, 
     226we took care of empty input data set by appropriately setting the 
     227GUI of a widget and sending an empty token to the output channel. 
     228 
     229 
     230Although our widget is now ready to test, for a final touch, let's 
     231design an icon for our widget. As specified in the widget header, we 
     232will call it :download:`DataSamplerA.svg <DataSamplerA.svg>` and will 
     233put it in `icons` subdirectory of `orangedemo` directory. 
     234 
     235With this we cen now go ahead and install the orangedemo package. We 
     236will do this by running :code:`python setup.py develop` command from 
     237the `Demo` directory. 
     238 
     239.. note:: 
     240   Depending on your python installation you might need 
     241   administrator/superuser privileges. 
    242242 
    243243For 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: 
     244widget toolbox called Demo. If we click on this pane, it displays an 
     245icon of our widget. Try to hover on it to see if the header and channel 
     246info was processed correctly: 
    248247 
    249248.. image:: samplewidgetontoolbox.png 
    250249 
    251250Now 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): 
     251Data pane) and load the iris.tab data set. We also put our Data 
     252Sampler widget on the scheme and open it (double click on the icon, 
     253or right-click and choose Open): 
    255254 
    256255.. image:: datasamplerAempty.png 
    257256 
    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: 
     257Now connect the File and Data Sampler widget (click on an output 
     258connector of the File widget, and drag the line to the input connector 
     259of the Data Sampler). If everything is ok, as soon as you release the 
     260mouse, the connection is established and, the token that was waiting 
     261on the output of the file widget was sent to the Data Sampler widget, 
     262which in turn updated its window: 
    265263 
    266264.. image:: datasamplerAupdated.png 
     
    292290        ow = OWDataSamplerA() 
    293291        ow.show() 
    294         dataset = orange.ExampleTable('iris.tab') 
     292        dataset = Orange.data.Table('iris.tab') 
    295293        ow.data(dataset) 
    296294        appl.exec_() 
    297295 
    298296These 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. 
     297widgets. Notice that we call the :func:`data` method directly. 
Note: See TracChangeset for help on using the changeset viewer.