source: orange/docs/extend-widgets/rst/graphing.rst @ 11593:6edc44eb9655

Revision 11593:6edc44eb9655, 5.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 10 months ago (diff)

Updated Widget development tutorial.

Line 
1###########################
2Graphing and Orange Widgets
3###########################
4
5The most fun widgets are of course those that include graphics. For
6this we either use Qt's :class:`QGraphicsScene` (widgets for tree and
7heat map visualizations, for instance, use this), or use a special
8control for drawing data plots as provided in Qwt library and :mod:`PyQwt`
9interface. Here we look at the latter, and extend our learning curve
10widget with a control that plots the curve.
11
12*****
13Plots
14*****
15
16Let us construct a widget with a following appearance:
17
18.. image:: learningcurve-plot.png
19
20There are two new elements from our previous incarnation of
21a learning curve widget: a control with a list of classifiers, and a
22graph with a plot of learning curves. From a list of classifiers we
23can select those to be displayed in the plot.
24
25The widget still provides learning curve table, but this is now
26offered in a tabbed pane together with a graph. The code for
27definition of the tabbed pane, and initialization of the graph is
28
29.. literalinclude:: OWLearningCurveC.py
30   :start-after: # ~SPHINX start main area tabs~
31   :end-before: # ~SPHINX end main area tabs~
32
33:class:`~OWGraph.OWGrap` is a convenience subclass of :class:`QwtPlot`
34and is imported from OWGraph module. For the graph, we use
35:func:`setAxisAutoScale` to request that the axis are automatically
36set in regard to the data that is plotted in the graph. We plot
37the graph in using the following code
38
39.. literalinclude:: OWLearningCurveC.py
40   :pyobject: OWLearningCurveC.drawLearningCurve
41
42This is simple. We store the curve returned from :obj:`addCurve` with a
43learner.
44
45.. warning::
46
47   This is a very bad design. Please do **not** store widget specific
48   data in the input objects.
49
50
51In this way, each learner also stores the current scores, which is a
52list of numbers to be plotted in Qwt graph. The details on how the
53plot is set are dealt with in :obj:`setGraphStyle` function:
54
55.. literalinclude:: OWLearningCurveC.py
56   :pyobject: OWLearningCurveC.setGraphStyle
57
58
59Notice that the color of the plot line that is specific to the
60learner is stored in its attribute :obj:`color`
61(:obj:`learner.color`). Who sets it and how? This we discuss in
62the following subsection.
63
64************************
65Colors in Orange Widgets
66************************
67
68Uniform assignment of colors across different widget is an
69important issue. When we plot the same data in different widgets, we
70expect that the color we used in a consistent way; for instance data
71instances of one class should be plotted in scatter plot and parallel
72axis plot using the same color. Developers are thus advised to use
73:obj:`ColorPaletteHSV`, which can be imported from :mod:`OWWidget`
74module. :obj:`ColorPaletteHSV` takes an
75integer as an parameter, and returns a list of corresponding number of
76colors. In our learning curve widget, we use it within a function that
77sets the list box with learners
78
79.. literalinclude:: OWLearningCurveC.py
80   :pyobject: OWLearningCurveC.updatellb
81
82The code above sets the items of the list box, where each item
83includes a learner and a small box in learner's color, which is in
84this widget also used as a sort of a legend for the graph. This box is
85returned by :obj:`ColorPixmap` function defined in
86:obj:`OWColorPalette.py`. Else, the classifier's list box control is
87defined in the initialization of the widget using
88
89.. literalinclude:: OWLearningCurveC.py
90   :start-after: # ~SPHINX start color cb~
91   :end-before: # ~SPHINX end color cb~
92
93Now, what is this :obj:`blockSelectionChanges`? Any time
94user makes a selection change in list box of classifiers, we want to
95invoke the procedure called
96:func:`learnerSelectionChanged`. But we want to perform
97actions there when changes in the list box are invoked from clicking
98by a user, and not by changing list box items from a program. This is
99why, every time we want :func:`learnerSelectionChanged` not to
100perform its function, we set :obj:`self.blockSelectionChanges`
101to 1.
102
103In our widget, :func:`learnerSelectionChanged` figures out
104if any curve should be removed from the graph (the user has just
105deselected the corresponding item in the list box) or added to the
106graph (the user just selected a learner)
107
108.. literalinclude:: OWLearningCurveC.py
109   :pyobject: OWLearningCurveC.learnerSelectionChanged
110
111..
112    def learnerSelectionChanged(self):
113        if self.blockSelectionChanges:
114            return
115        for (i,lt) in enumerate(self.learners):
116            l = lt[1]
117            if l.isSelected != (i in self.selectedLearners):
118                if l.isSelected: # learner was deselected
119                    l.curve.detach()
120                else: # learner was selected
121                    self.drawLearningCurve(l)
122                self.graph.replot()
123            l.isSelected = i in self.selectedLearners
124
125The complete code of this widget is available :download:`here <OWLearningCurveC.py>`.
126This is almost like a typical
127widget that is include in a standard Orange distribution, with a
128typical size just under 300 lines. Just some final cosmetics is needed
129to make this widget a standard one, including setting some graph
130properties (like line and point sizes, grid line control, etc.) and
131saving the graph to an output file.
Note: See TracBrowser for help on using the repository browser.