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

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

From HTML to Sphinx.

Line 
1###########################
2Using the new 'plot' module
3###########################
4
5Orange has a new plotting interface via the :obj:`OrangeWidgets.plot`
6module. The OWPlot class use Qt's graphics framework and was written specifically for
7Orange, so it contains methods more suitable to its data structures. It also provides
8most methods also found in the Qwt-based OWGraph, so switching from one base
9to another is quite easy.
10
11In the other example (:doc:`plots`) we showed how little modification
12is used to replace OWGraph with OWPlot.
13On the other hand, this example shows that using OWPlot from the start can be much easier.
14We will implement the simplest graph: a series of points with x and y coordinates,
15and different colors, sizes and shapes.
16
17
18.. image:: owplot_example.png
19
20The central method for plotting a series of points is
21:obj:`set_main_curve_data()`. It creates a curve and adds it to the plot, or
22just updates it if one already exists. Because it doesn't recreate the curve every time
23it's called, it can animate the transition from one data set to another.
24
25****************
26Setting the data
27****************
28
29To use this method, we must first convert the data to lists, one list for every property:
30X coordinates, Y coordinates, point colors, sizes and shapes.
31Each list except the coordinates can be empty, in which case the default value is used for all points.
32You can convert the data directly from your inputs, or use the classes in the
33:obj:`preprocess.scaling` module. ::
34
35    color_cont = (domain[c_i].varType == orange.VarTypes.Continuous)
36
37    for e in data:
38        x_data.append(e[x_i])
39        y_data.append(e[y_i])
40        c_data.append(e[c_i])
41        size = 5 + round(e[s_i])
42        s_data.append(size)
43
44    if color_cont:
45        m = min(c_data)
46        M = max(c_data)
47        c_data = [self.plot.continuous_palette[(v-m)/(M-m)] for v in c_data]
48    else:
49        c_data = [QColor(*self.plot.discrete_palette.getRGB(i)) for i in c_data]
50
51You can see that we have to adjust the color attribute in case that attribute is continuous.
52This is becouse OWPaletteGenerator only accepts values between 0 and 1 for such attributes.
53So we have to shrink the whole intervale to [0,1] before assigning any colors.
54
55Once we have the data lists, it's time to plot them::
56
57    self.plot.set_main_curve_data(x_data, y_data, color_data=c_data, size_data=s_data)
58    self.plot.replot()
59
60****************
61Showing a legend
62****************
63
64The points sure look nice, but how do we know what color represents what value?
65We show a legend. Unfortunately, using :obj:`set_main_curve_data()` can't
66guess the names and values of all attributes, so we must do it ourselves.
67Still, it isn't much work.
68
69First, we extend the loop for earlier to store colors and sizes in a set::
70
71    color_cont = (domain[c_i].varType == orange.VarTypes.Continuous)
72
73    legend_sizes = set()
74
75    for e in data:
76        x_data.append(e[x_i])
77        y_data.append(e[y_i])
78        c_data.append(e[c_i])
79        size = 5 + round(e[s_i])
80        s_data.append(size)
81
82        legend_sizes.add( (size, float(e[s_i])) )
83
84    if color_cont:
85        m = min(c_data)
86        M = max(c_data)
87        legend_colors = set(float(c) for c in c_data)
88        c_data = [self.plot.continuous_palette[(v-m)/(M-m)] for v in c_data]
89    else:
90        _colors = [self.plot.discrete_palette.getRGB(i) for i in c_data]
91        _values = set([float(c) for c in c_data])
92        legend_colors = zip([QColor(*c) for c in set(_colors)], _values)
93        c_data = [QColor(*c) for c in _colors]
94
95The logic for storing all possible sizes is quite simple:
96we create a set that holds them. Unfortunately, handling colors is bit more complex,
97because we first store (R,G,B) tuples in a set, and then convert them to QColor.
98Also, the approach differs for continuous or discrete color attributes.
99
100Now we have a set all colors and sizes we need to display in the legend.
101:obj:`legend_colors` and :obj:`legend_sizes` are both sets because
102we only want one legend item for each possible value, no matter how many
103points share that value. Now all that's left is adding the legend items::
104
105    self.plot.legend().clear()
106
107    if domain[s_i].varType == orange.VarTypes.Discrete:
108        for size, value in legend_sizes:
109            self.plot.legend().add_item( domain[s_i].name, domain[s_i].values[int(value)], OWPoint(OWPoint.Diamond, self.plot.color(OWPalette.Data), size) )
110
111    if color_cont:
112        self.plot.legend().add_color_gradient(domain[c_i].name, ("%.1f" % min(legend_colors), "%.1f" % max(legend_colors)))
113    else:
114        for color, value in legend_colors:
115            self.plot.legend().add_item( domain[c_i].name, domain[c_i].values[int(value)], OWPoint(OWPoint.Diamond, color, 5) )
116
117We only show a legend for the size attribute if it's discrete.
118However, we show the color attribute in both cases.
119If it's continuous, only one item is created with a color gradient.
120
121********************
122Running this example
123********************
124
125You can find the full code for the example `here <owplot_example.py>`_.
126This particular example has a timer, so that :obj:`set_data` is called every
127five seconds, and attributes are chosen at random each time, athough always
128from the same data set.
Note: See TracBrowser for help on using the repository browser.