source: orange/orange/doc/extend-widgets/graphing.htm @ 9398:a6b3d9c13ee0

Revision 9398:a6b3d9c13ee0, 7.0 KB checked in by mitar, 2 years ago (diff)

Renaming documentation for widgets developers.

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