VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuihlpgraphgooglechart.py@ 90802

Last change on this file since 90802 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.0 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuihlpgraphgooglechart.py 82968 2020-02-04 10:35:17Z vboxsync $
3
4"""
5Test Manager Web-UI - Graph Helpers - Implemented using Google Charts.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2020 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 82968 $"
30
31# Validation Kit imports.
32from common import utils, webutils;
33from testmanager.webui.wuihlpgraphbase import WuiHlpGraphBase;
34
35
36#*******************************************************************************
37#* Global Variables *
38#*******************************************************************************
39g_cGraphs = 0;
40
41class WuiHlpGraphGoogleChartsBase(WuiHlpGraphBase):
42 """ Base class for the Google Charts graphs. """
43 pass; # pylint: disable=unnecessary-pass
44
45
46class WuiHlpBarGraph(WuiHlpGraphGoogleChartsBase):
47 """
48 Bar graph.
49 """
50
51 def __init__(self, sId, oData, oDisp = None):
52 WuiHlpGraphGoogleChartsBase.__init__(self, sId, oData, oDisp);
53 self.fpMax = None;
54 self.fpMin = 0.0;
55 self.fYInverted = False;
56
57 def setRangeMax(self, fpMax):
58 """ Sets the max range."""
59 self.fpMax = float(fpMax);
60 return None;
61
62 def invertYDirection(self):
63 """ Inverts the direction of the Y-axis direction. """
64 self.fYInverted = True;
65 return None;
66
67 def renderGraph(self):
68 aoTable = self._oData.aoTable # type: WuiHlpGraphDataTable
69
70 # Seems material (google.charts.Bar) cannot change the direction on the Y-axis,
71 # so we cannot get bars growing downwards from the top like we want for the
72 # reports. The classic charts OTOH cannot put X-axis labels on the top, but
73 # we just drop them all together instead, saving a little space.
74 fUseMaterial = False;
75
76 # Unique on load function.
77 global g_cGraphs;
78 iGraph = g_cGraphs;
79 g_cGraphs += 1;
80
81 sHtml = '<div id="%s">\n' % ( self._sId, );
82 sHtml += '<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>\n' \
83 '<script type="text/javascript">\n' \
84 'google.charts.load("current", { packages: ["corechart", "bar"] });\n' \
85 'google.setOnLoadCallback(tmDrawBarGraph%u);\n' \
86 'function tmDrawBarGraph%u()\n' \
87 '{\n' \
88 ' var oGraph;\n' \
89 ' var dGraphOptions = \n' \
90 ' {\n' \
91 ' "title": "%s",\n' \
92 ' "hAxis": {\n' \
93 ' "title": "%s",\n' \
94 ' },\n' \
95 ' "vAxis": {\n' \
96 ' "direction": %s,\n' \
97 ' },\n' \
98 % ( iGraph,
99 iGraph,
100 webutils.escapeAttrJavaScriptStringDQ(self._sTitle) if self._sTitle is not None else '',
101 webutils.escapeAttrJavaScriptStringDQ(aoTable[0].sName) if aoTable and aoTable[0].sName else '',
102 '-1' if self.fYInverted else '1',
103 );
104 if fUseMaterial and self.fYInverted:
105 sHtml += ' "axes": { "x": { 0: { "side": "top" } }, "y": { "0": { "direction": -1, }, }, },\n';
106 sHtml += ' };\n';
107
108 # The data.
109 if self._oData.fHasStringValues and len(aoTable) > 1:
110 sHtml += ' var oData = new google.visualization.DataTable();\n';
111 # Column definitions.
112 sHtml += ' oData.addColumn("string", "%s");\n' \
113 % (webutils.escapeAttrJavaScriptStringDQ(aoTable[0].sName) if aoTable[0].sName else '',);
114 for iValue, oValue in enumerate(aoTable[0].aoValues):
115 oSampleValue = aoTable[1].aoValues[iValue];
116 if utils.isString(oSampleValue):
117 sHtml += ' oData.addColumn("string", "%s");\n' % (webutils.escapeAttrJavaScriptStringDQ(oValue),);
118 else:
119 sHtml += ' oData.addColumn("number", "%s");\n' % (webutils.escapeAttrJavaScriptStringDQ(oValue),);
120 sHtml += ' oData.addColumn({type: "string", role: "annotation"});\n';
121 # The data rows.
122 sHtml += ' oData.addRows([\n';
123 for oRow in aoTable[1:]:
124 if oRow.sName:
125 sRow = ' [ "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oRow.sName),);
126 else:
127 sRow = ' [ null';
128 for iValue, oValue in enumerate(oRow.aoValues):
129 if not utils.isString(oValue):
130 sRow += ', %s' % (oValue,);
131 else:
132 sRow += ', "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oValue),);
133 if oRow.asValues[iValue]:
134 sRow += ', "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oRow.asValues[iValue]),);
135 else:
136 sRow += ', null';
137 sHtml += sRow + '],\n';
138 sHtml += ' ]);\n';
139 else:
140 sHtml += ' var oData = google.visualization.arrayToDataTable([\n';
141 for oRow in aoTable:
142 sRow = ' [ "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oRow.sName),);
143 for oValue in oRow.aoValues:
144 if utils.isString(oValue):
145 sRow += ', "%s"' % (webutils.escapeAttrJavaScriptStringDQ(oValue),);
146 else:
147 sRow += ', %s' % (oValue,);
148 sHtml += sRow + '],\n';
149 sHtml += ' ]);\n';
150
151 # Create and draw.
152 if not fUseMaterial:
153 sHtml += ' oGraph = new google.visualization.ColumnChart(document.getElementById("%s"));\n' \
154 ' oGraph.draw(oData, dGraphOptions);\n' \
155 % ( self._sId, );
156 else:
157 sHtml += ' oGraph = new google.charts.Bar(document.getElementById("%s"));\n' \
158 ' oGraph.draw(oData, google.charts.Bar.convertOptions(dGraphOptions));\n' \
159 % ( self._sId, );
160
161 # clean and return.
162 sHtml += ' oData = null;\n' \
163 ' return true;\n' \
164 '};\n';
165
166 sHtml += '</script>\n' \
167 '</div>\n';
168 return sHtml;
169
170
171class WuiHlpLineGraph(WuiHlpGraphGoogleChartsBase):
172 """
173 Line graph.
174 """
175
176 ## @todo implement error bars.
177 kfNoErrorBarsSupport = True;
178
179 def __init__(self, sId, oData, oDisp = None, fErrorBarY = False):
180 # oData must be a WuiHlpGraphDataTableEx like object.
181 WuiHlpGraphGoogleChartsBase.__init__(self, sId, oData, oDisp);
182 self._cMaxErrorBars = 12;
183 self._fErrorBarY = fErrorBarY;
184
185 def setErrorBarY(self, fEnable):
186 """ Enables or Disables error bars, making this work like a line graph. """
187 self._fErrorBarY = fEnable;
188 return True;
189
190 def renderGraph(self): # pylint: disable=too-many-locals
191 fSlideFilter = True;
192
193 # Tooltips?
194 cTooltips = 0;
195 for oSeries in self._oData.aoSeries:
196 cTooltips += oSeries.asHtmlTooltips is not None;
197
198 # Unique on load function.
199 global g_cGraphs;
200 iGraph = g_cGraphs;
201 g_cGraphs += 1;
202
203 sHtml = '<div id="%s">\n' % ( self._sId, );
204 if fSlideFilter:
205 sHtml += ' <table><tr><td><div id="%s_graph"/></td></tr><tr><td><div id="%s_filter"/></td></tr></table>\n' \
206 % ( self._sId, self._sId, );
207
208 sHtml += '<script type="text/javascript" src="https://www.google.com/jsapi"></script>\n' \
209 '<script type="text/javascript">\n' \
210 'google.load("visualization", "1.0", { packages: ["corechart"%s] });\n' \
211 'google.setOnLoadCallback(tmDrawLineGraph%u);\n' \
212 'function tmDrawLineGraph%u()\n' \
213 '{\n' \
214 ' var fnResize;\n' \
215 ' var fnRedraw;\n' \
216 ' var idRedrawTimer = null;\n' \
217 ' var cxCur = getElementWidthById("%s") - 20;\n' \
218 ' var oGraph;\n' \
219 ' var oData = new google.visualization.DataTable();\n' \
220 ' var fpXYRatio = %u / %u;\n' \
221 ' var dGraphOptions = \n' \
222 ' {\n' \
223 ' "title": "%s",\n' \
224 ' "width": cxCur,\n' \
225 ' "height": Math.round(cxCur / fpXYRatio),\n' \
226 ' "pointSize": 2,\n' \
227 ' "fontSize": %u,\n' \
228 ' "hAxis": { "title": "%s", "minorGridlines": { count: 5 }},\n' \
229 ' "vAxis": { "title": "%s", "minorGridlines": { count: 5 }},\n' \
230 ' "theme": "maximized",\n' \
231 ' "tooltip": { "isHtml": %s }\n' \
232 ' };\n' \
233 % ( ', "controls"' if fSlideFilter else '',
234 iGraph,
235 iGraph,
236 self._sId,
237 self._cxGraph, self._cyGraph,
238 self._sTitle if self._sTitle is not None else '',
239 self._cPtFont * self._cDpiGraph / 72, # fudge
240 self._oData.sXUnit if self._oData.sXUnit else '',
241 self._oData.sYUnit if self._oData.sYUnit else '',
242 'true' if cTooltips > 0 else 'false',
243 );
244 if fSlideFilter:
245 sHtml += ' var oDashboard = new google.visualization.Dashboard(document.getElementById("%s"));\n' \
246 ' var oSlide = new google.visualization.ControlWrapper({\n' \
247 ' "controlType": "NumberRangeFilter",\n' \
248 ' "containerId": "%s_filter",\n' \
249 ' "options": {\n' \
250 ' "filterColumnIndex": 0,\n' \
251 ' "ui": { "width": getElementWidthById("%s") / 2 }, \n' \
252 ' }\n' \
253 ' });\n' \
254 % ( self._sId,
255 self._sId,
256 self._sId,);
257
258 # Data variables.
259 for iSeries, oSeries in enumerate(self._oData.aoSeries):
260 sHtml += ' var aSeries%u = [\n' % (iSeries,);
261 if oSeries.asHtmlTooltips is None:
262 sHtml += '[%s,%s]' % ( oSeries.aoXValues[0], oSeries.aoYValues[0],);
263 for i in range(1, len(oSeries.aoXValues)):
264 if (i & 16) == 0: sHtml += '\n';
265 sHtml += ',[%s,%s]' % ( oSeries.aoXValues[i], oSeries.aoYValues[i], );
266 else:
267 sHtml += '[%s,%s,"%s"]' \
268 % ( oSeries.aoXValues[0], oSeries.aoYValues[0],
269 webutils.escapeAttrJavaScriptStringDQ(oSeries.asHtmlTooltips[0]),);
270 for i in range(1, len(oSeries.aoXValues)):
271 if (i & 16) == 0: sHtml += '\n';
272 sHtml += ',[%s,%s,"%s"]' \
273 % ( oSeries.aoXValues[i], oSeries.aoYValues[i],
274 webutils.escapeAttrJavaScriptStringDQ(oSeries.asHtmlTooltips[i]),);
275
276 sHtml += '];\n'
277
278 sHtml += ' oData.addColumn("number", "%s");\n' % (self._oData.sXUnit if self._oData.sXUnit else '',);
279 cVColumns = 0;
280 for oSeries in self._oData.aoSeries:
281 sHtml += ' oData.addColumn("number", "%s");\n' % (oSeries.sName,);
282 if oSeries.asHtmlTooltips:
283 sHtml += ' oData.addColumn({"type": "string", "role": "tooltip", "p": {"html": true}});\n';
284 cVColumns += 1;
285 cVColumns += 1;
286 sHtml += 'var i;\n'
287
288 cVColumsDone = 0;
289 for iSeries, oSeries in enumerate(self._oData.aoSeries):
290 sVar = 'aSeries%u' % (iSeries,);
291 sHtml += ' for (i = 0; i < %s.length; i++)\n' \
292 ' {\n' \
293 ' oData.addRow([%s[i][0]%s,%s[i][1]%s%s]);\n' \
294 % ( sVar,
295 sVar,
296 ',null' * cVColumsDone,
297 sVar,
298 '' if oSeries.asHtmlTooltips is None else ',%s[i][2]' % (sVar,),
299 ',null' * (cVColumns - cVColumsDone - 1 - (oSeries.asHtmlTooltips is not None)),
300 );
301 sHtml += ' }\n' \
302 ' %s = null\n' \
303 % (sVar,);
304 cVColumsDone += 1 + (oSeries.asHtmlTooltips is not None);
305
306 # Create and draw.
307 if fSlideFilter:
308 sHtml += ' oGraph = new google.visualization.ChartWrapper({\n' \
309 ' "chartType": "LineChart",\n' \
310 ' "containerId": "%s_graph",\n' \
311 ' "options": dGraphOptions\n' \
312 ' });\n' \
313 ' oDashboard.bind(oSlide, oGraph);\n' \
314 ' oDashboard.draw(oData);\n' \
315 % ( self._sId, );
316 else:
317 sHtml += ' oGraph = new google.visualization.LineChart(document.getElementById("%s"));\n' \
318 ' oGraph.draw(oData, dGraphOptions);\n' \
319 % ( self._sId, );
320
321 # Register a resize handler for redrawing the graph, using a timer to delay it.
322 sHtml += ' fnRedraw = function() {\n' \
323 ' var cxNew = getElementWidthById("%s") - 6;\n' \
324 ' if (Math.abs(cxNew - cxCur) > 8)\n' \
325 ' {\n' \
326 ' cxCur = cxNew;\n' \
327 ' dGraphOptions["width"] = cxNew;\n' \
328 ' dGraphOptions["height"] = Math.round(cxNew / fpXYRatio);\n' \
329 ' oGraph.draw(oData, dGraphOptions);\n' \
330 ' }\n' \
331 ' clearTimeout(idRedrawTimer);\n' \
332 ' idRedrawTimer = null;\n' \
333 ' return true;\n' \
334 ' };\n' \
335 ' fnResize = function() {\n' \
336 ' if (idRedrawTimer != null) { clearTimeout(idRedrawTimer); } \n' \
337 ' idRedrawTimer = setTimeout(fnRedraw, 512);\n' \
338 ' return true;\n' \
339 ' };\n' \
340 ' if (window.attachEvent)\n' \
341 ' { window.attachEvent("onresize", fnResize); }\n' \
342 ' else if (window.addEventListener)\n' \
343 ' { window.addEventListener("resize", fnResize, true); }\n' \
344 % ( self._sId, );
345
346 # clean up what the callbacks don't need.
347 sHtml += ' oData = null;\n' \
348 ' aaaSeries = null;\n';
349
350 # done;
351 sHtml += ' return true;\n' \
352 '};\n';
353
354 sHtml += '</script>\n' \
355 '</div>\n';
356 return sHtml;
357
358
359class WuiHlpLineGraphErrorbarY(WuiHlpLineGraph):
360 """
361 Line graph with an errorbar for the Y axis.
362 """
363
364 def __init__(self, sId, oData, oDisp = None):
365 WuiHlpLineGraph.__init__(self, sId, oData, fErrorBarY = True);
366
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette