source: orange/docs/extend-widgets/rst/graphing.rst @ 11049:f4dd8dbc57bb

Revision 11049:f4dd8dbc57bb, 6.7 KB checked in by Miha Stajdohar <miha.stajdohar@…>, 16 months ago (diff)

From HTML to Sphinx.

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 OWGraph module. For the graph, we use :obj:`setAxisAutoScale` to
42request that the axis are automatically set in regard to the data that
43is plotted in the graph. We plot the graph in using the following
44code::
45
46    def drawLearningCurve(self, learner):
47        if not self.data: return
48        curve = self.graph.addCurve(learner.name, xData=self.curvePoints, yData=learner.score, autoScale=True)
49
50        learner.curve = curve
51        self.setGraphStyle(learner)
52        self.graph.replot()
53
54This is simple. We store the curve returned from :obj:`addCurve` with a
55learner, and use a trick allowed in Orange that we can simply store
56this as a new attribute to the learning object. By default, Orange
57would give a warning of the type ::
58
59    c:\Python23\Lib\site-packages\orange\OrangeWidgets\Test\OWLearningCurveC.py:269:
60     AttributeWarning: 'curve' is not a builtin attribute of 'kNNLearner'
61      setattr(learner, "curve", curve)
62
63but we surpress such warnings with a line ::
64
65    warnings.filterwarnings("ignore", ".*builtin attribute.*", orange.AttributeWarning)
66
67in the initialization part of the widget. In this way, each learner
68also stores the current scores, which is a list of numbers to be
69plotted in Qwt graph. The details on how the plot is set are dealt
70with in :obj:`setGraphStyle` function:` ::
71
72    def setGraphStyle(self, learner):
73        curve = learner.curve
74        if self.graphDrawLines:
75            curve.setStyle(QwtPlotCurve.Lines)
76        else:
77            curve.setStyle(QwtPlotCurve.NoCurve)
78        curve.setSymbol(QwtSymbol(QwtSymbol.Ellipse, \
79          QBrush(QColor(0,0,0)), QPen(QColor(0,0,0)),
80          QSize(self.graphPointSize, self.graphPointSize)))
81        curve.setPen(QPen(learner.color, 5))
82
83Notice that the color of the plot line that is specific to the
84learner is stored in its attribute :obj:`color`
85(:obj:`learner.color`). Who sets it and how? This we discuss in
86the following subsection.
87
88************************
89Colors in Orange Widgets
90************************
91
92Uniform assignment of colors across different widget is an
93important issue. When we plot the same data in different widgets, we
94expect that the color we used in a consistent way; for instance data
95instances of one class should be plotted in scatter plot and parallel
96axis plot using the same color. Developers are thus advised to use
97:obj:`ColorPaletteHSV`, which is provided as a method within
98:obj:`OWWidget` class. :obj:`ColorPaletteHSV` takes an
99integer as an attribute, and returns a list of corresponding number of
100colors. In our learning curve widget, we use it within a function that
101sets the list box with learners::
102
103    def updatellb(self):
104        self.blockSelectionChanges = 1
105        self.llb.clear()
106        colors = ColorPaletteHSV(len(self.learners))
107        for (i,lt) in enumerate(self.learners):
108            l = lt[1]
109            item = QListWidgetItem(ColorPixmap(colors[i]), l.name)
110            self.llb.addItem(item)
111            item.setSelected(l.isSelected)
112            l.color = colors[i]
113        self.blockSelectionChanges = 0
114
115The code above sets the items of the list box, where each item
116includes a learner and a small box in learner's color, which is in
117this widget also used as a sort of a legend for the graph. This box is
118returned by :obj:`ColorPixmap` function defined in
119:obj:`OWColorPalette.py`. Else, the classifier's list box control is
120defined in the initialization of the widget using::
121
122    self.cbox = OWGUI.widgetBox(self.controlArea, "Learners")
123    self.llb = OWGUI.listBox(self.cbox, self, "selectedLearners", selectionMode=QListWidget.MultiSelection, callback=self.learnerSelectionChanged)
124
125    self.llb.setMinimumHeight(50)
126    self.blockSelectionChanges = 0
127
128Now, what is this :obj:`blockSelectionChanges`? Any time
129user makes a selection change in list box of classifiers, we want to
130invoke the procedure called
131:obj:`learnerSelectionChanged`. But we want to perform
132actions there when changes in the list box are invoked from clicking
133by a user, and not by changing list box items from a program. This is
134why, every time we want :obj:`learnerSelectionChanged` not to
135perform its function, we set :obj:`self.blockSelectionChanges`
136to 1.
137
138In our widget, :obj:`learnerSelectionChanged` figures out
139if any curve should be removed from the graph (the user has just
140deselected the corresponding item in the list box) or added to the
141graph (the user just selected a learner)::
142
143    def learnerSelectionChanged(self):
144        if self.blockSelectionChanges: return
145        for (i,lt) in enumerate(self.learners):
146            l = lt[1]
147            if l.isSelected != (i in self.selectedLearners):
148                if l.isSelected: # learner was deselected
149                    l.curve.detach()
150                else: # learner was selected
151                    self.drawLearningCurve(l)
152                self.graph.replot()
153            l.isSelected = i in self.selectedLearners
154
155The complete code of this widget is available `here <OWLearningCurveC.py>`_.
156This is almost like a typical
157widget that is include in a standard Orange distribution, with a
158typical size just under 300 lines. Just some final cosmetics is needed
159to make this widget a standard one, including setting some graph
160properties (like line and point sizes, grid line control, etc.) and
161saving the graph to an output file.
Note: See TracBrowser for help on using the repository browser.