source: orange/Orange/doc/extend-widgets/context-settings.htm @ 9671:a7b056375472

Revision 9671:a7b056375472, 13.7 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

Line 
1<html>
2<head>
3<title>Orange Widgets: Settings and Controls</title>
4<link rel=stylesheet HREF="../style.css" type="text/css">
5<link rel=stylesheet href="style-print.css" type="text/css" media=print>
6</head>
7<body>
8
9<H1>Context-Dependent Settings</H1>
10
11<P>You have already learned about <a href="settings.htm">storing
12widget settings</a>. But there's more: some settings are context
13dependent. Open Orange Canvas and observe the scatter plot - feed it
14some data, select two attributes for x- and y-axis, select some
15examples... and then give it some other data. Your settings get
16lost. Or do they? Well, change back to the original data and you will
17see the same two attributes on the axes and even the same examples
18selected.</P>
19
20<P>What happens is that Orange remembers the settings (chosen
21attributes etc.) and ties them with the data domain. The next time it
22gets the data from the same (or similar enough) domain, the settings
23will be reused. The history of an arbitrary number of domains can be
24stored in this manner.</P>
25
26<P>To learn how to do it yourself, consider the widget below used for
27selecting a subset of attributes and the class attributes (note that a
28better widget for this task is already included in your Orange
29instalation).</P>
30
31<img src="attributesampler.png">
32
33<P>The widget gets examples on the input and outputs the same examples
34with the attributes and the class chosen by the user. We'd like to
35somehow store the user's selection.</P>
36
37<P>Here's the widget's <code>__init__</code> function.</P>
38
39<p class="header">part of <a href="OWAttributeSampler.py">OWAttributeSampler.py</a></p>
40<xmp class="code">def __init__(self, parent=None, signalManager=None):
41    OWWidget.__init__(self, parent, signalManager, 'AttributeSampler')
42
43    self.inputs = [("Examples", ExampleTable, self.dataset)]
44    self.outputs = [("Examples", ExampleTable)]
45
46    self.icons = self.createAttributeIconDict()
47
48    self.attributeList = []
49    self.selectedAttributes = []
50    self.classAttribute = None
51    self.loadSettings()
52
53    OWGUI.listBox(self.controlArea, self, "selectedAttributes", "attributeList", box="Selected attributes", selectionMode = QListWidget.ExtendedSelection)
54    OWGUI.separator(self.controlArea)
55    self.classAttrCombo = OWGUI.comboBox(self.controlArea, self, "classAttribute", box="Class attribute")
56    OWGUI.separator(self.controlArea)
57    OWGUI.button(self.controlArea, self, "Commit", callback = self.outputData)
58
59    self.resize(150,400)
60</xmp>
61
62<P>Note that we are strictly using controls from OWGUI. As for the
63usual settings, if you use Qt controls directly, their state won't get
64synchronized with the widget's internal variables and vice versa. The
65list box is associated with two variables: <code>attributeList</code>
66contains the attributes (as tuples with the name and the type), and
67<code>selectedAttributes</code> is a list with indices of selected
68attributes. Combo box will put the index of the chosen class attribute
69into <code>classAttribute</code>.</P>
70
71<P>When the widget gets the data, a function <code>dataset</code> is
72called.</P>
73
74<p class="header">part of <a href="OWAttributeSampler.py">OWAttributeSampler.py</a></p>
75<xmp class="code">def dataset(self, data):
76    self.classAttrCombo.clear()
77    if data:
78        self.attributeList = [(attr.name, attr.varType) for attr in data.domain]
79        self.selectedAttributes = []
80        for attrName, attrType in self.attributeList:
81            self.classAttrCombo.addItem(self.icons[attrType], attrName)
82        self.classAttribute = 0
83    else:
84        self.attributeList = []
85        self.selectedAttributes = []
86        self.classAttrCombo.addItem("")
87
88    self.data = data
89    self.outputData()
90
91
92def outputData(self):
93    if not self.data:
94        self.send("Examples", None)
95    else:
96        newDomain = orange.Domain([self.data.domain[i] for i in self.selectedAttributes], self.data.domain[self.classAttribute])
97        newData = orange.ExampleTable(newDomain, self.data)
98        self.send("Examples", newData)
99</xmp>
100
101<P>Nothing special here (yet). We fill the list box, deselect all
102attributes and set the last attribute to be the class
103attribute. Output data is put into a separate function because it's
104called by <code>dataset</code> and when the user presses the "Apply"
105button.</P>
106
107<P>The widgets is functionally complete, but it doesn't remember
108anything. You can try to put the three variables
109(<code>attributeList</code>, <code>selectedAttributes</code> and
110<code>classAttribute</code>) in the <code>settingsList</code>, as
111you've seen on the page about settings, but it won't work. It can't:
112settings are saved and loaded only when the widget is created, not
113every time it gets a new signal. Besides, the ordinary settings in the
114<code>settingsList</code> are not context dependent, so the widget
115would usually try to assign, say, the class attribute which doesn't
116exist in the actual domain at all.</P>
117
118<P>To make the setting dependent on the context, we put
119<xmp class="code">contextHandlers = {"": DomainContextHandler("", [
120            ContextField("classAttribute", DomainContextHandler.Required),
121            ContextField("attributeList", DomainContextHandler.List +
122                                          DomainContextHandler.SelectedRequired,
123                         selected="selectedAttributes")])}
124</xmp>
125at the same place where we usually declare <code>settingsList</code>.</P>
126
127<p>"Contexts" may be defined by different things, but settings most
128commonly depend on the domain of the examples. Such settings are taken
129by a context handler of type <code>DomainContextHandler</code>. We
130tell it about the fields that it should control: the first is
131<code>classAttribute</code>, and the other two form a pair,
132<code>attributeList</code> contains the attributes and
133<code>selectedAttributes</code> is the selection. The latter has the
134flag <code>DomainContextHandler.List</code> which tells the context
135handler that the property in question is a list, not an ordinary
136field.</P>
137
138<P>And what is "<code>Required</code>" and
139"<code>SelectedRequired</code>"? These are important in domain
140matching. Say that you loaded the car data, selected attributes
141<code>price</code>, <code>maint</code> and <code>lug_boot</code> and
142set the class attribute to <code>acc</code>. Now you load a modified
143car data in which the attribute <code>doors</code> is missing. Can the
144settings be reused? Sure, <code>doors</code> was not selected, so this
145attribute is not really needed. The new domain is thus not exactly the
146same as the one with which the context was saved, but nothing
147essential is missing so the context is loaded.</P>
148
149<P>A different thing is if the new set misses attributes
150<code>price</code> or <code>acc</code>; in this case, the old settings
151cannot and should not be reused. So, this is the meaning of
152<code>DomainContextHandler.Required</code> and
153<code>DomainContextHandler.SelectedRequired</code>: a stored context
154doesn't match the new data if the data lacks the attribute that the
155context stores as "<code>classAttribute</code>". And, the new data
156also has to have all the attributes that were selected in the stored
157context. If any of the other attributes misses, it doesn't matter, the
158context will still match and be used.</P>
159
160<P>As you have guessed, we can also have optional attributes
161(<code>DomainContextHandler.Optional</code>); sometimes certain
162attribute doesn't really matter, so if it is present in the domain,
163it's gonna be used, otherwise not. And for the list, we could say
164<code>DomainContextHandler.List + DomainContextHandler.Required</code>
165in which case all the attributes on the list would be required for the
166domain to match.</P>
167
168<P>The default flag is <code>DomainContextHandler.Required</code>, and there are other shortcuts for declaring the context, too. The above code could be simplified as
169<xmp class="code">contextHandlers = {"": DomainContextHandler("", [
170            "classAttribute",
171            ContextField("attributeList", DomainContextHandler.SelectedRequiredList,
172                         selected="selectedAttributes")])}
173</xmp>
174(More about these shortcuts in the <a href="settings-technical.htm">technical information about settings</a>).
175
176<P>Why the dictionary and the empty string as the key? A widget can
177have multiple contexts, depending, usually, on multiple input
178signals. These contexts can be named, but the default name is empty
179string. A case in which we would really need multiple contexts has yet
180to appear, so you shall mostly declare the contexts as above. (Note
181that we gave the name twice - the first empty string is for the key in
182the dictionary and with the second we tell the context handler its own
183name.)</P>
184
185<P>So much for declaration of contexts. The ordinary, context
186independent settings load and save automatically as the widget is
187created and destroyed. Context dependent settings are stored and
188restored when the context changes, usually due to receiving a signal
189with a new data set. This unfortunately cannot be handled
190automatically - you have to add the calls of the appropriate context
191changing functions yourself. Here's what you have to do with the
192function <code>dataset</code>
193
194<p class="header">part of <a href="OWAttributeSampler.py">OWAttributeSampler.py</a></p>
195<xmp class="code">def dataset(self, data):
196    self.closeContext()
197
198    self.classAttrCombo.clear()
199    if data:
200        self.attributeList = [(attr.name, attr.varType) for attr in data.domain]
201        self.selectedAttributes = []
202        for attrName, attrType in self.attributeList:
203            self.classAttrCombo.addItem(self.icons[attrType], attrName)
204        self.classAttribute = 0
205    else:
206        self.attributeList = []
207        self.selectedAttributes = []
208        self.classAttrCombo.addItem("")
209
210    self.openContext("", data)
211
212    self.data = data
213    self.outputData()
214</xmp>
215
216<P>We added only two lines. First, before you change any controls in the widget, you need to call <code>self.closeContext</code> (the function has an optional argument, the context name, but since we use the default name, an empty string, we can omit it). This reads the data from the widget into the stored context. Then the function proceeds as before: the controls (the list box and combo box) are filled in as if there were no context handling (this is important, so once again: widget should be set up as if there were not context dependent settings). When the controls are put in a consistent state, we call <code>self.openContext</code>. The first argument is the context name and the second is the object from which the handler reads the context. In case of <code>DomainContextHandler</code> this can be either a domain or the data. <code>openContext</code> will make the context handler search through the stored context for the one that (best) matches the data, and if one is find the widget's state is set accordingly (that is, the list boxes are filled, attributes in it are selected etc.). If no context is found, a new context is established and the data from widget is copied to the context.</P>
217
218<P>What can be stored as a context dependent setting? Anything, even
219the state of check boxes if you want to. But don't do that. Make
220<em>some</em> of your checkboxes context dependent (so that they will
221change when the new data arrives) and the use of the widget will be
222completely chaotic since nobody will know what changes and what stays
223the same. Make <em>all</em> your controls context dependent and the
224widget will become useless as it will reset to the defaults every time
225some new data arrives. Bottom line, regarding to controls, make as
226little context dependent settings as possible - the context dependent
227controls will usually be limited to list boxes and combo boxes that
228store attribute names.
229
230<P>But there are other things that you can put into the context. Just
231remember the scatter plot's ability to remember the example selection
232- which is surely not stored in a simple list box. How does it do it?
233Here are two methods it defines:
234
235<xmp class="code">def settingsFromWidgetCallback(self, handler, context):
236        context.selectionPolygons = []
237        for key in self.graph.selectionCurveKeyList:
238            curve = self.graph.curve(key)
239            xs = [curve.x(i) for i in range(curve.dataSize())]
240            ys = [curve.y(i) for i in range(curve.dataSize())]
241            context.selectionPolygons.append((xs, ys))
242
243    def settingsToWidgetCallback(self, handler, context):
244        selections = context.selectionPolygons
245        for (xs, ys) in selections:
246            c = SelectionCurve(self.graph)
247            c.setData(xs,ys)
248            key = self.graph.insertCurve(c)
249            self.graph.selectionCurveKeyList.append(key)
250</xmp>
251</P>
252
253<p><code>settingsFromWidgetCallback</code> is called by the context
254handler to copy the settings from the widget to the context, and
255<code>settingsToWidgetCallback</code> writes the settings back to the
256widget. Their arguments, besides <code>self</code>, are the context
257handler and the context. Whatever
258<code>settingsFromWidgetCallback</code> stores into the
259<code>context</code>, stays there, gets saved when the canvas is
260closed and loaded when it's opened
261again. <code>setttingsToWidgetCallback</code> can read these fields
262and restore the widget's state (the example selection, in this case)
263accordingly.</p>
264
265<P><code>selectionPolygons</code> is not registered by the context
266handler the way we registered <code>attributeList</code>,
267<code>selectedAttributes</code> and <code>classAttribute</code> above,
268since the context handler doesn't need to know and care about
269<code>selectionPolygons</code>.</P>
270
271<P>When writing such callback functions make sure that the data you
272store is picklable and short enough, so you won't blow up the .ini
273files that store these settings.</P>
274
275<P>For more information about context handling, see the <a
276href="settings-technical.htm">technical information about
277settings</a>.</P>
278
279</body>
280</html>
Note: See TracBrowser for help on using the repository browser.