source: orange/docs/extend-widgets/rst/graphing.rst @ 11408:c2d2400b6a90

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

Fixes for Widgets Development documentation.

2Graphing and Orange Widgets
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.
17Let us construct a widget with a following appearance:
19.. image:: learningcurve-plot.png
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.
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::
30    # start of content (right) area
31    tabs = OWGUI.tabWidget(self.mainArea)
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()
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
46    def drawLearningCurve(self, learner):
47        if not return
48        curve = self.graph.addCurve(, xData=self.curvePoints, yData=learner.score, autoScale=True)
50        learner.curve = curve
51        self.setGraphStyle(learner)
52        self.graph.replot()
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 ::
59    c:\Python23\Lib\site-packages\orange\OrangeWidgets\Test\
60     AttributeWarning: 'curve' is not a builtin attribute of 'kNNLearner'
61      setattr(learner, "curve", curve)
63but we surpress such warnings with a line ::
65    warnings.filterwarnings("ignore", ".*builtin attribute.*", orange.AttributeWarning)
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:` ::
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))
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.
89Colors in Orange Widgets
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:mod:`OWWidget` module. :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::
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]),
110            self.llb.addItem(item)
111            item.setSelected(l.isSelected)
112            l.color = colors[i]
113        self.blockSelectionChanges = 0
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:``. Else, the classifier's list box control is
120defined in the initialization of the widget using::
122    self.cbox = OWGUI.widgetBox(self.controlArea, "Learners")
123    self.llb = OWGUI.listBox(self.cbox, self, "selectedLearners",
124                             selectionMode=QListWidget.MultiSelection,
125                             callback=self.learnerSelectionChanged)
127    self.llb.setMinimumHeight(50)
128    self.blockSelectionChanges = 0
130Now, what is this :obj:`blockSelectionChanges`? Any time
131user makes a selection change in list box of classifiers, we want to
132invoke the procedure called
133:obj:`learnerSelectionChanged`. But we want to perform
134actions there when changes in the list box are invoked from clicking
135by a user, and not by changing list box items from a program. This is
136why, every time we want :obj:`learnerSelectionChanged` not to
137perform its function, we set :obj:`self.blockSelectionChanges`
138to 1.
140In our widget, :obj:`learnerSelectionChanged` figures out
141if any curve should be removed from the graph (the user has just
142deselected the corresponding item in the list box) or added to the
143graph (the user just selected a learner)::
145    def learnerSelectionChanged(self):
146        if self.blockSelectionChanges:
147            return
148        for (i,lt) in enumerate(self.learners):
149            l = lt[1]
150            if l.isSelected != (i in self.selectedLearners):
151                if l.isSelected: # learner was deselected
152                    l.curve.detach()
153                else: # learner was selected
154                    self.drawLearningCurve(l)
155                self.graph.replot()
156            l.isSelected = i in self.selectedLearners
158The complete code of this widget is available :download:`here <>`.
159This is almost like a typical
160widget that is include in a standard Orange distribution, with a
161typical size just under 300 lines. Just some final cosmetics is needed
162to make this widget a standard one, including setting some graph
163properties (like line and point sizes, grid line control, etc.) and
164saving the graph to an output file.
Note: See TracBrowser for help on using the repository browser.