summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py')
-rwxr-xr-xsrc/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py341
1 files changed, 341 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py
new file mode 100755
index 00000000..cb5cf893
--- /dev/null
+++ b/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphmatplotlib.py
@@ -0,0 +1,341 @@
+# -*- coding: utf-8 -*-
+# $Id: wuihlpgraphmatplotlib.py $
+
+"""
+Test Manager Web-UI - Graph Helpers - Implemented using matplotlib.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+# Standard Python Import and extensions installed on the system.
+import re;
+import sys;
+if sys.version_info[0] >= 3:
+ from io import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias
+else:
+ from StringIO import StringIO as StringIO; # pylint: disable=import-error,no-name-in-module,useless-import-alias
+
+import matplotlib; # pylint: disable=import-error
+matplotlib.use('Agg'); # Force backend.
+import matplotlib.pyplot; # pylint: disable=import-error
+from numpy import arange as numpy_arange; # pylint: disable=no-name-in-module,import-error,wrong-import-order
+
+# Validation Kit imports.
+from testmanager.webui.wuihlpgraphbase import WuiHlpGraphBase;
+
+
+class WuiHlpGraphMatplotlibBase(WuiHlpGraphBase):
+ """ Base class for the matplotlib graphs. """
+
+ def __init__(self, sId, oData, oDisp):
+ WuiHlpGraphBase.__init__(self, sId, oData, oDisp);
+ self._fXkcdStyle = True;
+
+ def setXkcdStyle(self, fEnabled = True):
+ """ Enables xkcd style graphs for implementations that supports it. """
+ self._fXkcdStyle = fEnabled;
+ return True;
+
+ def _createFigure(self):
+ """
+ Wrapper around matplotlib.pyplot.figure that feeds the figure the
+ basic graph configuration.
+ """
+ if self._fXkcdStyle and matplotlib.__version__ > '1.2.9':
+ matplotlib.pyplot.xkcd(); # pylint: disable=no-member
+ matplotlib.rcParams.update({'font.size': self._cPtFont});
+
+ oFigure = matplotlib.pyplot.figure(figsize = (float(self._cxGraph) / self._cDpiGraph,
+ float(self._cyGraph) / self._cDpiGraph),
+ dpi = self._cDpiGraph);
+ return oFigure;
+
+ def _produceSvg(self, oFigure, fTightLayout = True):
+ """ Creates an SVG string from the given figure. """
+ oOutput = StringIO();
+ if fTightLayout:
+ oFigure.tight_layout();
+ oFigure.savefig(oOutput, format = 'svg');
+
+ if self._oDisp and self._oDisp.isBrowserGecko('20100101'):
+ # This browser will stretch images to fit if no size or width is given.
+ sSubstitute = r'\1 \3 reserveAspectRatio="xMidYMin meet"';
+ else:
+ # Chrome and IE likes to have the sizes as well as the viewBox.
+ sSubstitute = r'\1 \3 reserveAspectRatio="xMidYMin meet" \2 \4';
+ return re.sub(r'(<svg) (height="\d+pt") (version="\d+.\d+" viewBox="\d+ \d+ \d+ \d+") (width="\d+pt")',
+ sSubstitute,
+ oOutput.getvalue().decode('utf8'),
+ count = 1);
+
+class WuiHlpBarGraph(WuiHlpGraphMatplotlibBase):
+ """
+ Bar graph.
+ """
+
+ def __init__(self, sId, oData, oDisp = None):
+ WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp);
+ self.fpMax = None;
+ self.fpMin = 0.0;
+ self.cxBarWidth = None;
+
+ def setRangeMax(self, fpMax):
+ """ Sets the max range."""
+ self.fpMax = float(fpMax);
+ return None;
+
+ def invertYDirection(self):
+ """ Inverts the direction of the Y-axis direction. """
+ ## @todo self.fYInverted = True;
+ return None;
+
+ def renderGraph(self): # pylint: disable=too-many-locals
+ aoTable = self._oData.aoTable;
+
+ #
+ # Extract/structure the required data.
+ #
+ aoSeries = [];
+ for j in range(len(aoTable[1].aoValues)):
+ aoSeries.append([]);
+ asNames = [];
+ oXRange = numpy_arange(self._oData.getGroupCount());
+ fpMin = self.fpMin;
+ fpMax = self.fpMax;
+ if self.fpMax is None:
+ fpMax = float(aoTable[1].aoValues[0]);
+
+ for i in range(1, len(aoTable)):
+ asNames.append(aoTable[i].sName);
+ for j, oValue in enumerate(aoTable[i].aoValues):
+ fpValue = float(oValue);
+ aoSeries[j].append(fpValue);
+ if fpValue < fpMin:
+ fpMin = fpValue;
+ if fpValue > fpMax:
+ fpMax = fpValue;
+
+ fpMid = fpMin + (fpMax - fpMin) / 2.0;
+
+ if self.cxBarWidth is None:
+ self.cxBarWidth = 1.0 / (len(aoTable[0].asValues) + 1.1);
+
+ # Render the PNG.
+ oFigure = self._createFigure();
+ oSubPlot = oFigure.add_subplot(1, 1, 1);
+
+ aoBars = [];
+ for i, _ in enumerate(aoSeries):
+ sColor = self.calcSeriesColor(i);
+ aoBars.append(oSubPlot.bar(oXRange + self.cxBarWidth * i,
+ aoSeries[i],
+ self.cxBarWidth,
+ color = sColor,
+ align = 'edge'));
+
+ #oSubPlot.set_title('Title')
+ #oSubPlot.set_xlabel('X-axis')
+ #oSubPlot.set_xticks(oXRange + self.cxBarWidth);
+ oSubPlot.set_xticks(oXRange);
+ oLegend = oSubPlot.legend(aoTable[0].asValues, loc = 'best', fancybox = True);
+ oLegend.get_frame().set_alpha(0.5);
+ oSubPlot.set_xticklabels(asNames, ha = "left");
+ #oSubPlot.set_ylabel('Y-axis')
+ oSubPlot.set_yticks(numpy_arange(fpMin, fpMax + (fpMax - fpMin) / 10 * 0, fpMax / 10));
+ oSubPlot.grid(True);
+ fpPadding = (fpMax - fpMin) * 0.02;
+ for i, _ in enumerate(aoBars):
+ aoRects = aoBars[i]
+ for j, _ in enumerate(aoRects):
+ oRect = aoRects[j];
+ fpValue = float(aoTable[j + 1].aoValues[i]);
+ if fpValue <= fpMid:
+ oSubPlot.text(oRect.get_x() + oRect.get_width() / 2.0,
+ oRect.get_height() + fpPadding,
+ aoTable[j + 1].asValues[i],
+ ha = 'center', va = 'bottom', rotation = 'vertical', alpha = 0.6, fontsize = 'small');
+ else:
+ oSubPlot.text(oRect.get_x() + oRect.get_width() / 2.0,
+ oRect.get_height() - fpPadding,
+ aoTable[j + 1].asValues[i],
+ ha = 'center', va = 'top', rotation = 'vertical', alpha = 0.6, fontsize = 'small');
+
+ return self._produceSvg(oFigure);
+
+
+
+
+class WuiHlpLineGraph(WuiHlpGraphMatplotlibBase):
+ """
+ Line graph.
+ """
+
+ def __init__(self, sId, oData, oDisp = None, fErrorBarY = False):
+ # oData must be a WuiHlpGraphDataTableEx like object.
+ WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp);
+ self._cMaxErrorBars = 12;
+ self._fErrorBarY = fErrorBarY;
+
+ def setErrorBarY(self, fEnable):
+ """ Enables or Disables error bars, making this work like a line graph. """
+ self._fErrorBarY = fEnable;
+ return True;
+
+ def renderGraph(self): # pylint: disable=too-many-locals
+ aoSeries = self._oData.aoSeries;
+
+ oFigure = self._createFigure();
+ oSubPlot = oFigure.add_subplot(1, 1, 1);
+ if self._oData.sYUnit is not None:
+ oSubPlot.set_ylabel(self._oData.sYUnit);
+ if self._oData.sXUnit is not None:
+ oSubPlot.set_xlabel(self._oData.sXUnit);
+
+ cSeriesNames = 0;
+ cYMin = 1000;
+ cYMax = 0;
+ for iSeries, oSeries in enumerate(aoSeries):
+ sColor = self.calcSeriesColor(iSeries);
+ cYMin = min(cYMin, min(oSeries.aoYValues));
+ cYMax = max(cYMax, max(oSeries.aoYValues));
+ if not self._fErrorBarY:
+ oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, color = sColor);
+ elif len(oSeries.aoXValues) > self._cMaxErrorBars:
+ if matplotlib.__version__ < '1.3.0':
+ oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues, color = sColor);
+ else:
+ oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues,
+ yerr = [oSeries.aoYErrorBarBelow, oSeries.aoYErrorBarAbove],
+ errorevery = len(oSeries.aoXValues) / self._cMaxErrorBars,
+ color = sColor );
+ else:
+ oSubPlot.errorbar(oSeries.aoXValues, oSeries.aoYValues,
+ yerr = [oSeries.aoYErrorBarBelow, oSeries.aoYErrorBarAbove],
+ color = sColor);
+ cSeriesNames += oSeries.sName is not None;
+
+ if cYMin != 0 or cYMax != 0:
+ oSubPlot.set_ylim(bottom = 0);
+
+ if cSeriesNames > 0:
+ oLegend = oSubPlot.legend([oSeries.sName for oSeries in aoSeries], loc = 'best', fancybox = True);
+ oLegend.get_frame().set_alpha(0.5);
+
+ if self._sTitle is not None:
+ oSubPlot.set_title(self._sTitle);
+
+ if self._cxGraph >= 256:
+ oSubPlot.minorticks_on();
+ oSubPlot.grid(True, 'major', axis = 'both');
+ oSubPlot.grid(True, 'both', axis = 'x');
+
+ if True: # pylint: disable=using-constant-test
+ # oSubPlot.axis('off');
+ #oSubPlot.grid(True, 'major', axis = 'none');
+ #oSubPlot.grid(True, 'both', axis = 'none');
+ matplotlib.pyplot.setp(oSubPlot, xticks = [], yticks = []);
+
+ return self._produceSvg(oFigure);
+
+
+class WuiHlpLineGraphErrorbarY(WuiHlpLineGraph):
+ """
+ Line graph with an errorbar for the Y axis.
+ """
+
+ def __init__(self, sId, oData, oDisp = None):
+ WuiHlpLineGraph.__init__(self, sId, oData, fErrorBarY = True);
+
+
+class WuiHlpMiniSuccessRateGraph(WuiHlpGraphMatplotlibBase):
+ """
+ Mini rate graph.
+ """
+
+ def __init__(self, sId, oData, oDisp = None):
+ """
+ oData must be a WuiHlpGraphDataTableEx like object, but only aoSeries,
+ aoSeries[].aoXValues, and aoSeries[].aoYValues will be used. The
+ values are expected to be a percentage, i.e. values between 0 and 100.
+ """
+ WuiHlpGraphMatplotlibBase.__init__(self, sId, oData, oDisp);
+ self.setFontSize(6);
+
+ def renderGraph(self): # pylint: disable=too-many-locals
+ assert len(self._oData.aoSeries) == 1;
+ oSeries = self._oData.aoSeries[0];
+
+ # hacking
+ #self.setWidth(512);
+ #self.setHeight(128);
+ # end
+
+ oFigure = self._createFigure();
+ from mpl_toolkits.axes_grid.axislines import SubplotZero; # pylint: disable=import-error
+ oAxis = SubplotZero(oFigure, 111);
+ oFigure.add_subplot(oAxis);
+
+ # Disable all the normal axis.
+ oAxis.axis['right'].set_visible(False)
+ oAxis.axis['top'].set_visible(False)
+ oAxis.axis['bottom'].set_visible(False)
+ oAxis.axis['left'].set_visible(False)
+
+ # Use the zero axis instead.
+ oAxis.axis['yzero'].set_axisline_style('-|>');
+ oAxis.axis['yzero'].set_visible(True);
+ oAxis.axis['xzero'].set_axisline_style('-|>');
+ oAxis.axis['xzero'].set_visible(True);
+
+ if oSeries.aoYValues[-1] == 100:
+ sColor = 'green';
+ elif oSeries.aoYValues[-1] > 75:
+ sColor = 'yellow';
+ else:
+ sColor = 'red';
+ oAxis.plot(oSeries.aoXValues, oSeries.aoYValues, '.-', color = sColor, linewidth = 3);
+ oAxis.fill_between(oSeries.aoXValues, oSeries.aoYValues, facecolor = sColor, alpha = 0.5)
+
+ oAxis.set_xlim(left = -0.01);
+ oAxis.set_xticklabels([]);
+ oAxis.set_xmargin(1);
+
+ oAxis.set_ylim(bottom = 0, top = 100);
+ oAxis.set_yticks([0, 50, 100]);
+ oAxis.set_ylabel('%');
+ #oAxis.set_yticklabels([]);
+ oAxis.set_yticklabels(['', '%', '']);
+
+ return self._produceSvg(oFigure, False);
+