source: orange/docs/extend-widgets/rst/graphing.rst @ 11424:ccb4a5fac5e1

Revision 11424:ccb4a5fac5e1, 6.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

More fixes to Widgets Development documentation

Line 
1###########################
2Graphing and Orange Widgets
3###########################
4
5The most fun widgets are of course those that include graphics. For
6this we either use control called canvas, which is Qt's general
7control for doing any graphics of choice (widgets for tree and heat map
8visualizations, for instance, use this), or use a special control for
9drawing data plots as provided in Qwt library and PyQwt
10interface. Here we look at the latter, and extend our learning curve
11widget with a control that plots the curve.
12
13*****
14Plots
15*****
16
17Let us construct a widget with a following appearance:
18
19.. image:: learningcurve-plot.png
20
21There are two new elements from our previous incarnation of
22a learning curve widget: a control with a list of classifiers, and a
23graph with a plot of learning curves. From a list of classifiers we
24can select those to be displayed in the plot.
25
26The widget still provides learning curve table, but this is now
27offered in a tabbed pane together with a graph. The code for
28definition of the tabbed pane, and initialization of the graph is::
29
30    # start of content (right) area
31    tabs = OWGUI.tabWidget(self.mainArea)
32
33    # graph widget
34    tab = OWGUI.createTabPage(tabs, "Graph")
35    self.graph = OWGraph(tab)
36    self.graph.setAxisAutoScale(QwtPlot.xBottom)
37    self.graph.setAxisAutoScale(QwtPlot.yLeft)
38    tab.layout().addWidget(self.graph)
39    self.setGraphGrid()
40
41:obj:`OWGrap` is a convenience subclass of QwtPlot and is imported from
42OWGraph module. For the graph, we use :obj:`setAxisAutoScale` to
43request that the axis are automatically set in regard to the data that
44is plotted in the graph. We plot the graph in using the following
45code::
46
47    def drawLearningCurve(self, learner):
48        if not self.data: return
49        curve = self.graph.addCurve(learner.name, xData=self.curvePoints, yData=learner.score, autoScale=True)
50
51        learner.curve = curve
52        self.setGraphStyle(learner)
53        self.graph.replot()
54
55This is simple. We store the curve returned from :obj:`addCurve` with a
56learner, and use a trick allowed in Orange that we can simply store
57this as a new attribute to the learning object. By default, Orange
58would give a warning of the type ::
59
60    c:\Python23\Lib\site-packages\orange\OrangeWidgets\Test\OWLearningCurveC.py:269:
61     AttributeWarning: 'curve' is not a builtin attribute of 'kNNLearner'
62      setattr(learner, "curve", curve)
63
64but we surpress such warnings with a line ::
65
66    warnings.filterwarnings("ignore", ".*builtin attribute.*", orange.AttributeWarning)
67
68in the initialization part of the widget. In this way, each learner
69also stores the current scores, which is a list of numbers to be
70plotted in Qwt graph. The details on how the plot is set are dealt
71with in :obj:`setGraphStyle` function:` ::
72
73    def setGraphStyle(self, learner):
74        curve = learner.curve
75        if self.graphDrawLines:
76            curve.setStyle(QwtPlotCurve.Lines)
77        else:
78            curve.setStyle(QwtPlotCurve.NoCurve)
79        curve.setSymbol(QwtSymbol(QwtSymbol.Ellipse, \
80          QBrush(QColor(0,0,0)), QPen(QColor(0,0,0)),
81          QSize(self.graphPointSize, self.graphPointSize)))
82        curve.setPen(QPen(learner.color, 5))
83
84Notice that the color of the plot line that is specific to the
85learner is stored in its attribute :obj:`color`
86(:obj:`learner.color`). Who sets it and how? This we discuss in
87the following subsection.
88
89************************
90Colors in Orange Widgets
91************************
92
93Uniform assignment of colors across different widget is an
94important issue. When we plot the same data in different widgets, we
95expect that the color we used in a consistent way; for instance data
96instances of one class should be plotted in scatter plot and parallel
97axis plot using the same color. Developers are thus advised to use
98:obj:`ColorPaletteHSV`, which is provided as a method within
99:mod:`OWWidget` module. :obj:`ColorPaletteHSV` takes an
100integer as an attribute, and returns a list of corresponding number of
101colors. In our learning curve widget, we use it within a function that
102sets the list box with learners::
103
104    def updatellb(self):
105        self.blockSelectionChanges = 1
106        self.llb.clear()
107        colors = ColorPaletteHSV(len(self.learners))
108        for (i,lt) in enumerate(self.learners):
109            l = lt[1]
110            item = QListWidgetItem(ColorPixmap(colors[i]), l.name)
111            self.llb.addItem(item)
112            item.setSelected(l.isSelected)
113            l.color = colors[i]
114        self.blockSelectionChanges = 0
115
116The code above sets the items of the list box, where each item
117includes a learner and a small box in learner's color, which is in
118this widget also used as a sort of a legend for the graph. This box is
119returned by :obj:`ColorPixmap` function defined in
120:obj:`OWColorPalette.py`. Else, the classifier's list box control is
121defined in the initialization of the widget using::
122
123    self.cbox = OWGUI.widgetBox(self.controlArea, "Learners")
124    self.llb = OWGUI.listBox(self.cbox, self, "selectedLearners",
125                             selectionMode=QListWidget.MultiSelection,
126                             callback=self.learnerSelectionChanged)
127
128    self.llb.setMinimumHeight(50)
129    self.blockSelectionChanges = 0
130
131Now, what is this :obj:`blockSelectionChanges`? Any time
132user makes a selection change in list box of classifiers, we want to
133invoke the procedure called
134:obj:`learnerSelectionChanged`. But we want to perform
135actions there when changes in the list box are invoked from clicking
136by a user, and not by changing list box items from a program. This is
137why, every time we want :obj:`learnerSelectionChanged` not to
138perform its function, we set :obj:`self.blockSelectionChanges`
139to 1.
140
141In our widget, :obj:`learnerSelectionChanged` figures out
142if any curve should be removed from the graph (the user has just
143deselected the corresponding item in the list box) or added to the
144graph (the user just selected a learner)::
145
146    def learnerSelectionChanged(self):
147        if self.blockSelectionChanges:
148            return
149        for (i,lt) in enumerate(self.learners):
150            l = lt[1]
151            if l.isSelected != (i in self.selectedLearners):
152                if l.isSelected: # learner was deselected
153                    l.curve.detach()
154                else: # learner was selected
155                    self.drawLearningCurve(l)
156                self.graph.replot()
157            l.isSelected = i in self.selectedLearners
158
159The complete code of this widget is available :download:`here <OWLearningCurveC.py>`.
160This is almost like a typical
161widget that is include in a standard Orange distribution, with a
162typical size just under 300 lines. Just some final cosmetics is needed
163to make this widget a standard one, including setting some graph
164properties (like line and point sizes, grid line control, etc.) and
165saving the graph to an output file.
Note: See TracBrowser for help on using the repository browser.