1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: wuihlpgraphmatplotlib.py 62484 2016-07-22 18:35:33Z vboxsync $
|
---|
3 |
|
---|
4 | """
|
---|
5 | Test Manager Web-UI - Graph Helpers - Implemented using matplotlib.
|
---|
6 | """
|
---|
7 |
|
---|
8 | __copyright__ = \
|
---|
9 | """
|
---|
10 | Copyright (C) 2012-2016 Oracle Corporation
|
---|
11 |
|
---|
12 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
13 | available from http://www.virtualbox.org. This file is free software;
|
---|
14 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
15 | General Public License (GPL) as published by the Free Software
|
---|
16 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
17 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
18 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
19 |
|
---|
20 | The contents of this file may alternatively be used under the terms
|
---|
21 | of the Common Development and Distribution License Version 1.0
|
---|
22 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
23 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
24 | CDDL are applicable instead of those of the GPL.
|
---|
25 |
|
---|
26 | You may elect to license modified versions of this file under the
|
---|
27 | terms and conditions of either the GPL or the CDDL or both.
|
---|
28 | """
|
---|
29 | __version__ = "$Revision: 62484 $"
|
---|
30 |
|
---|
31 | # Standard Python Import and extensions installed on the system.
|
---|
32 | import re;
|
---|
33 | import StringIO;
|
---|
34 |
|
---|
35 | import matplotlib; # pylint: disable=F0401
|
---|
36 | matplotlib.use('Agg'); # Force backend.
|
---|
37 | import matplotlib.pyplot; # pylint: disable=F0401
|
---|
38 | from numpy import arange as numpy_arange; # pylint: disable=E0611,E0401
|
---|
39 |
|
---|
40 | # Validation Kit imports.
|
---|
41 | from testmanager.webui.wuihlpgraphbase import WuiHlpGraphBase;
|
---|
42 |
|
---|
43 |
|
---|
44 | class WuiHlpGraphMatplotlibBase(WuiHlpGraphBase):
|
---|
45 | """ Base class for the matplotlib graphs. """
|
---|
46 |
|
---|
47 | def __init__(self, sId, oData, oDisp):
|
---|
48 | WuiHlpGraphBase.__init__(self, sId, oData, oDisp);
|
---|
49 | self._fXkcdStyle = True;
|
---|
50 |
|
---|
51 | def setXkcdStyle(self, fEnabled = True):
|
---|
52 | """ Enables xkcd style graphs for implementations that supports it. """
|
---|
53 | self._fXkcdStyle = fEnabled;
|
---|
54 | return True;
|
---|
55 |
|
---|
56 | def _createFigure(self):
|
---|
57 | """
|
---|
58 | Wrapper around matplotlib.pyplot.figure that feeds the figure the
|
---|
59 | basic graph configuration.
|
---|
60 | """
|
---|
61 | if self._fXkcdStyle and matplotlib.__version__ > '1.2.9':
|
---|
62 | matplotlib.pyplot.xkcd(); # pylint: disable=E1101
|
---|
63 | matplotlib.rcParams.update({'font.size': self._cPtFont});
|
---|
64 |
|
---|
65 | oFigure = matplotlib.pyplot.figure(figsize = (float(self._cxGraph) / self._cDpiGraph,
|
---|
66 | float(self._cyGraph) / self._cDpiGraph),
|
---|
67 | dpi = self._cDpiGraph);
|
---|
68 | return oFigure;
|
---|
69 |
|
---|
70 | def _produceSvg(self, oFigure, fTightLayout = True):
|
---|
71 | """ Creates an SVG string from the given figure. """
|
---|
72 | oOutput = StringIO.StringIO();
|
---|
73 | if fTightLayout:
|
---|
74 | oFigure.tight_layout();
|
---|
75 | oFigure.savefig(oOutput, format = 'svg');
|
---|
76 |
|
---|
77 | if self._oDisp and self._oDisp.isBrowserGecko('20100101'):
|
---|
78 | # This browser will stretch images to fit if no size or width is given.
|
---|
79 | sSubstitute = r'\1 \3 reserveAspectRatio="xMidYMin meet"';
|
---|
80 | else:
|
---|
81 | # Chrome and IE likes to have the sizes as well as the viewBox.
|
---|
82 | sSubstitute = r'\1 \3 reserveAspectRatio="xMidYMin meet" \2 \4';
|
---|
83 | return re.sub(r'(<svg) (height="\d+pt") (version="\d+.\d+" viewBox="\d+ \d+ \d+ \d+") (width="\d+pt")',
|
---|
84 | sSubstitute,
|
---|
85 | oOutput.getvalue().decode('utf8'),
|
---|
86 | count = 1);
|
---|
87 |
|
---|
88 | class WuiHlpBarGraph(WuiHlpGraphMatplotlibBase):
|
---|
89 | """
|
---|
90 | Bar graph.
|
---|
91 | """
|
---|
92 |
|
---|
93 | def __init__(self, sId, oData, oDisp = None):
|
---|
94 | WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp);
|
---|
95 | self.fpMax = None;
|
---|
96 | self.fpMin = 0.0;
|
---|
97 | self.cxBarWidth = None;
|
---|
98 |
|
---|
99 | def setRangeMax(self, fpMax):
|
---|
100 | """ Sets the max range."""
|
---|
101 | self.fpMax = float(fpMax);
|
---|
102 | return None;
|
---|
103 |
|
---|
104 | def renderGraph(self): # pylint: disable=R0914
|
---|
105 | aoTable = self._oData.aoTable;
|
---|
106 |
|
---|
107 | #
|
---|
108 | # Extract/structure the required data.
|
---|
109 | #
|
---|
110 | aoSeries = list();
|
---|
111 | for j in range(len(aoTable[1].aoValues)):
|
---|
112 | aoSeries.append(list());
|
---|
113 | asNames = list();
|
---|
114 | oXRange = numpy_arange(self._oData.getGroupCount());
|
---|
115 | fpMin = self.fpMin;
|
---|
116 | fpMax = self.fpMax;
|
---|
117 | if self.fpMax is None:
|
---|
118 | fpMax = float(aoTable[1].aoValues[0]);
|
---|
119 |
|
---|
120 | for i in range(1, len(aoTable)):
|
---|
121 | asNames.append(aoTable[i].sName);
|
---|
122 | for j in range(len(aoTable[i].aoValues)):
|
---|
123 | fpValue = float(aoTable[i].aoValues[j]);
|
---|
124 | aoSeries[j].append(fpValue);
|
---|
125 | if fpValue < fpMin:
|
---|
126 | fpMin = fpValue;
|
---|
127 | if fpValue > fpMax:
|
---|
128 | fpMax = fpValue;
|
---|
129 |
|
---|
130 | fpMid = fpMin + (fpMax - fpMin) / 2.0;
|
---|
131 |
|
---|
132 | if self.cxBarWidth is None:
|
---|
133 | self.cxBarWidth = 1.0 / (len(aoTable[0].asValues) + 1.1);
|
---|
134 |
|
---|
135 | # Render the PNG.
|
---|
136 | oFigure = self._createFigure();
|
---|
137 | oSubPlot = oFigure.add_subplot(1, 1, 1);
|
---|
138 |
|
---|
139 | aoBars = list();
|
---|
140 | for i, _ in enumerate(aoSeries):
|
---|
141 | sColor = self.calcSeriesColor(i);
|
---|
142 | aoBars.append(oSubPlot.bar(oXRange + self.cxBarWidth * i,
|
---|
143 | aoSeries[i],
|
---|
144 | self.cxBarWidth,
|
---|
145 | color = sColor,
|
---|
146 | align = 'edge'));
|
---|
147 |
|
---|
148 | #oSubPlot.set_title('Title')
|
---|
149 | #oSubPlot.set_xlabel('X-axis')
|
---|
150 | #oSubPlot.set_xticks(oXRange + self.cxBarWidth);
|
---|
151 | oSubPlot.set_xticks(oXRange);
|
---|
152 | oLegend = oSubPlot.legend(aoTable[0].asValues, loc = 'best', fancybox = True);
|
---|
153 | oLegend.get_frame().set_alpha(0.5);
|
---|
154 | oSubPlot.set_xticklabels(asNames, ha = "left");
|
---|
155 | #oSubPlot.set_ylabel('Y-axis')
|
---|
156 | oSubPlot.set_yticks(numpy_arange(fpMin, fpMax + (fpMax - fpMin) / 10 * 0, fpMax / 10));
|
---|
157 | oSubPlot.grid(True);
|
---|
158 | fpPadding = (fpMax - fpMin) * 0.02;
|
---|
159 | for i, _ in enumerate(aoBars):
|
---|
160 | aoRects = aoBars[i]
|
---|
161 | for j, _ in enumerate(aoRects):
|
---|
162 | oRect = aoRects[j];
|
---|
163 | fpValue = float(aoTable[j + 1].aoValues[i]);
|
---|
164 | if fpValue <= fpMid:
|
---|
165 | oSubPlot.text(oRect.get_x() + oRect.get_width() / 2.0,
|
---|
166 | oRect.get_height() + fpPadding,
|
---|
167 | aoTable[j + 1].asValues[i],
|
---|
168 | ha = 'center', va = 'bottom', rotation = 'vertical', alpha = 0.6, fontsize = 'small');
|
---|
169 | else:
|
---|
170 | oSubPlot.text(oRect.get_x() + oRect.get_width() / 2.0,
|
---|
171 | oRect.get_height() - fpPadding,
|
---|
172 | aoTable[j + 1].asValues[i],
|
---|
173 | ha = 'center', va = 'top', rotation = 'vertical', alpha = 0.6, fontsize = 'small');
|
---|
174 |
|
---|
175 | return self._produceSvg(oFigure);
|
---|
176 |
|
---|
177 |
|
---|
178 |
|
---|
179 |
|
---|
180 | class WuiHlpLineGraph(WuiHlpGraphMatplotlibBase):
|
---|
181 | """
|
---|
182 | Line graph.
|
---|
183 | """
|
---|
184 |
|
---|
185 | def __init__(self, sId, oData, oDisp = None, fErrorBarY = False):
|
---|
186 | # oData must be a WuiHlpGraphDataTableEx like object.
|
---|
187 | WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp);
|
---|
188 | self._cMaxErrorBars = 12;
|
---|
189 | self._fErrorBarY = fErrorBarY;
|
---|
190 |
|
---|
191 | def setErrorBarY(self, fEnable):
|
---|
192 | """ Enables or Disables error bars, making this work like a line graph. """
|
---|
193 | self._fErrorBarY = fEnable;
|
---|
194 | return True;
|
---|
195 |
|
---|
196 | def renderGraph(self): # pylint: disable=R0914
|
---|
197 | aoSeries = self._oData.aoSeries;
|
---|
198 |
|
---|
199 | oFigure = self._createFigure();
|
---|
200 | oSubPlot = oFigure.add_subplot(1, 1, 1);
|
---|
201 | if self._oData.sYUnit is not None:
|
---|
202 | oSubPlot.set_ylabel(self._oData.sYUnit);
|
---|
203 | if self._oData.sXUnit is not None:
|
---|
204 | oSubPlot.set_xlabel(self._oData.sXUnit);
|
---|
205 |
|
---|
206 | cSeriesNames = 0;
|
---|
207 | cYMin = 1000;
|
---|
208 | cYMax = 0;
|
---|
209 | for iSeries, oSeries in enumerate(aoSeries):
|
---|
210 | sColor = self.calcSeriesColor(iSeries);
|
---|
211 | cYMin = min(cYMin, min(oSeries.aoYValues));
|
---|
212 | cYMax = max(cYMax, max(oSeries.aoYValues));
|
---|
213 | if not self._fErrorBarY:
|
---|
214 | oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, color = sColor);
|
---|
215 | elif len(oSeries.aoXValues) > self._cMaxErrorBars:
|
---|
216 | if matplotlib.__version__ < '1.3.0':
|
---|
217 | oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, color = sColor);
|
---|
218 | else:
|
---|
219 | oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues,
|
---|
220 | yerr = [oSeries.aoYErrorBarBelow, oSeries.aoYErrorBarAbove],
|
---|
221 | errorevery = len(oSeries.aoXValues) / self._cMaxErrorBars,
|
---|
222 | color = sColor );
|
---|
223 | else:
|
---|
224 | oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues,
|
---|
225 | yerr = [oSeries.aoYErrorBarBelow, oSeries.aoYErrorBarAbove],
|
---|
226 | color = sColor);
|
---|
227 | cSeriesNames += oSeries.sName is not None;
|
---|
228 |
|
---|
229 | if cYMin != 0 or cYMax != 0:
|
---|
230 | oSubPlot.set_ylim(bottom = 0);
|
---|
231 |
|
---|
232 | if cSeriesNames > 0:
|
---|
233 | oLegend = oSubPlot.legend([oSeries.sName for oSeries in aoSeries], loc = 'best', fancybox = True);
|
---|
234 | oLegend.get_frame().set_alpha(0.5);
|
---|
235 |
|
---|
236 | if self._sTitle is not None:
|
---|
237 | oSubPlot.set_title(self._sTitle);
|
---|
238 |
|
---|
239 | if self._cxGraph >= 256:
|
---|
240 | oSubPlot.minorticks_on();
|
---|
241 | oSubPlot.grid(True, 'major', axis = 'both');
|
---|
242 | oSubPlot.grid(True, 'both', axis = 'x');
|
---|
243 |
|
---|
244 | if True: # pylint: disable=W0125
|
---|
245 | # oSubPlot.axis('off');
|
---|
246 | #oSubPlot.grid(True, 'major', axis = 'none');
|
---|
247 | #oSubPlot.grid(True, 'both', axis = 'none');
|
---|
248 | matplotlib.pyplot.setp(oSubPlot, xticks = [], yticks = []);
|
---|
249 |
|
---|
250 | return self._produceSvg(oFigure);
|
---|
251 |
|
---|
252 |
|
---|
253 | class WuiHlpLineGraphErrorbarY(WuiHlpLineGraph):
|
---|
254 | """
|
---|
255 | Line graph with an errorbar for the Y axis.
|
---|
256 | """
|
---|
257 |
|
---|
258 | def __init__(self, sId, oData, oDisp = None):
|
---|
259 | WuiHlpLineGraph.__init__(self, sId, oData, fErrorBarY = True);
|
---|
260 |
|
---|
261 |
|
---|
262 | class WuiHlpMiniSuccessRateGraph(WuiHlpGraphMatplotlibBase):
|
---|
263 | """
|
---|
264 | Mini rate graph.
|
---|
265 | """
|
---|
266 |
|
---|
267 | def __init__(self, sId, oData, oDisp = None):
|
---|
268 | """
|
---|
269 | oData must be a WuiHlpGraphDataTableEx like object, but only aoSeries,
|
---|
270 | aoSeries[].aoXValues, and aoSeries[].aoYValues will be used. The
|
---|
271 | values are expected to be a percentage, i.e. values between 0 and 100.
|
---|
272 | """
|
---|
273 | WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp);
|
---|
274 | self.setFontSize(6);
|
---|
275 |
|
---|
276 | def renderGraph(self): # pylint: disable=R0914
|
---|
277 | assert len(self._oData.aoSeries) == 1;
|
---|
278 | oSeries = self._oData.aoSeries[0];
|
---|
279 |
|
---|
280 | # hacking
|
---|
281 | #self.setWidth(512);
|
---|
282 | #self.setHeight(128);
|
---|
283 | # end
|
---|
284 |
|
---|
285 | oFigure = self._createFigure();
|
---|
286 | from mpl_toolkits.axes_grid.axislines import SubplotZero; # pylint: disable=E0401
|
---|
287 | oAxis = SubplotZero(oFigure, 111);
|
---|
288 | oFigure.add_subplot(oAxis);
|
---|
289 |
|
---|
290 | # Disable all the normal axis.
|
---|
291 | oAxis.axis['right'].set_visible(False)
|
---|
292 | oAxis.axis['top'].set_visible(False)
|
---|
293 | oAxis.axis['bottom'].set_visible(False)
|
---|
294 | oAxis.axis['left'].set_visible(False)
|
---|
295 |
|
---|
296 | # Use the zero axis instead.
|
---|
297 | oAxis.axis['yzero'].set_axisline_style('-|>');
|
---|
298 | oAxis.axis['yzero'].set_visible(True);
|
---|
299 | oAxis.axis['xzero'].set_axisline_style('-|>');
|
---|
300 | oAxis.axis['xzero'].set_visible(True);
|
---|
301 |
|
---|
302 | if oSeries.aoYValues[-1] == 100:
|
---|
303 | sColor = 'green';
|
---|
304 | elif oSeries.aoYValues[-1] > 75:
|
---|
305 | sColor = 'yellow';
|
---|
306 | else:
|
---|
307 | sColor = 'red';
|
---|
308 | oAxis.plot(oSeries.aoXValues, oSeries.aoYValues, '.-', color = sColor, linewidth = 3);
|
---|
309 | oAxis.fill_between(oSeries.aoXValues, oSeries.aoYValues, facecolor = sColor, alpha = 0.5)
|
---|
310 |
|
---|
311 | oAxis.set_xlim(left = -0.01);
|
---|
312 | oAxis.set_xticklabels([]);
|
---|
313 | oAxis.set_xmargin(1);
|
---|
314 |
|
---|
315 | oAxis.set_ylim(bottom = 0, top = 100);
|
---|
316 | oAxis.set_yticks([0, 50, 100]);
|
---|
317 | oAxis.set_ylabel('%');
|
---|
318 | #oAxis.set_yticklabels([]);
|
---|
319 | oAxis.set_yticklabels(['', '%', '']);
|
---|
320 |
|
---|
321 | return self._produceSvg(oFigure, False);
|
---|
322 |
|
---|