d8952b53ff6a841bb367264d1765c106bbe86ced
[WebKit-https.git] / PerformanceTests / Animometer / resources / debug-runner / graph.js
1 Utilities.extendObject(window.benchmarkController, {
2     layoutCounter: 0,
3
4     updateGraphData: function(testResult, testData, options)
5     {
6         var element = document.getElementById("test-graph-data");
7         element.innerHTML = "";
8         element._testResult = testResult;
9         element._testData = testData;
10         element._options = options;
11         document.querySelector("hr").style.width = this.layoutCounter++ + "px";
12
13         var margins = new Insets(30, 30, 30, 40);
14         var size = Point.elementClientSize(element).subtract(margins.size);
15
16         this.createTimeGraph(testResult, testData[Strings.json.samples][Strings.json.controller], testData[Strings.json.marks], testData[Strings.json.controller], options, margins, size);
17         this.onTimeGraphOptionsChanged();
18
19         var hasComplexitySamples = !!testData[Strings.json.samples][Strings.json.complexity];
20         this._showOrHideNodes(hasComplexitySamples, "form[name=graph-type]");
21         if (hasComplexitySamples) {
22             document.forms["graph-type"].elements["type"] = "complexity";
23             this.createComplexityGraph(testResult, testData[Strings.json.controller], testData[Strings.json.samples], options, margins, size);
24             this.onComplexityGraphOptionsChanged();
25         }
26
27         this.onGraphTypeChanged();
28     },
29
30     _addRegressionLine: function(parent, xScale, yScale, points, range, isAlongYAxis)
31     {
32         var polygon = [];
33         var line = []
34         var xRange = isAlongYAxis ? range : 0;
35         var yRange = isAlongYAxis ? 0 : range;
36         for (var i = 0; i < points.length; ++i) {
37             var point = points[i];
38             var x;
39             if (xRange instanceof Array)
40                 x = xRange[0];
41             else
42                 x = point[0] + xRange;
43             polygon.push(xScale(x), yScale(point[1] + yRange));
44             line.push(xScale(point[0]), yScale(point[1]));
45         }
46         for (var i = points.length - 1; i >= 0; --i) {
47             var point = points[i];
48             var x;
49             if (xRange instanceof Array)
50                 x = xRange[1];
51             else
52                 x = point[0] - xRange;
53             polygon.push(xScale(x), yScale(point[1] - yRange));
54         }
55         parent.append("polygon")
56             .attr("points", polygon.join(","));
57         parent.append("line")
58             .attr("x1", line[0])
59             .attr("y1", line[1])
60             .attr("x2", line[2])
61             .attr("y2", line[3]);
62     },
63
64     _addRegression: function(data, svg, xScale, yScale)
65     {
66         svg.append("circle")
67             .attr("cx", xScale(data.segment1[1][0]))
68             .attr("cy", yScale(data.segment1[1][1]))
69             .attr("r", 5);
70         this._addRegressionLine(svg, xScale, yScale, data.segment1, data.stdev);
71         this._addRegressionLine(svg, xScale, yScale, data.segment2, data.stdev);
72     },
73
74     createComplexityGraph: function(result, timeRegressions, data, options, margins, size)
75     {
76         var svg = d3.select("#test-graph-data").append("svg")
77             .attr("id", "complexity-graph")
78             .attr("class", "hidden")
79             .attr("width", size.width + margins.left + margins.right)
80             .attr("height", size.height + margins.top + margins.bottom)
81             .append("g")
82                 .attr("transform", "translate(" + margins.left + "," + margins.top + ")");
83
84         var timeSamples = data[Strings.json.controller];
85
86         var xMin = 100000, xMax = 0;
87         if (timeRegressions) {
88             timeRegressions.forEach(function(regression) {
89                 for (var i = regression.startIndex; i <= regression.endIndex; ++i) {
90                     xMin = Math.min(xMin, timeSamples[i].complexity);
91                     xMax = Math.max(xMax, timeSamples[i].complexity);
92                 }
93             });
94         } else {
95             xMin = d3.min(timeSamples, function(s) { return s.complexity; });
96             xMax = d3.max(timeSamples, function(s) { return s.complexity; });
97         }
98
99         var xScale = d3.scale.linear()
100             .range([0, size.width])
101             .domain([xMin, xMax]);
102         var yScale = d3.scale.linear()
103             .range([size.height, 0])
104             .domain([1000/20, 1000/60]);
105
106         var xAxis = d3.svg.axis()
107             .scale(xScale)
108             .orient("bottom");
109         var yAxis = d3.svg.axis()
110             .scale(yScale)
111             .tickValues([1000/20, 1000/25, 1000/30, 1000/35, 1000/40, 1000/45, 1000/50, 1000/55, 1000/60])
112             .tickFormat(function(d) { return (1000 / d).toFixed(0); })
113             .orient("left");
114
115         // x-axis
116         svg.append("g")
117             .attr("class", "x axis")
118             .attr("transform", "translate(0," + size.height + ")")
119             .call(xAxis);
120
121         // y-axis
122         svg.append("g")
123             .attr("class", "y axis")
124             .call(yAxis);
125
126         // time result
127         var mean = svg.append("g")
128             .attr("class", "mean complexity");
129         var timeResult = result[Strings.json.controller];
130         var yMin = yScale.domain()[0], yMax = yScale.domain()[1];
131         this._addRegressionLine(mean, xScale, yScale, [[timeResult.average, yMin], [timeResult.average, yMax]], timeResult.stdev, true);
132
133         // regression
134         this._addRegression(result[Strings.json.complexity], svg.append("g").attr("class", "regression raw"), xScale, yScale);
135         this._addRegression(result[Strings.json.complexityAverage], svg.append("g").attr("class", "regression average"), xScale, yScale);
136
137         var bootstrapResult = result[Strings.json.complexity][Strings.json.bootstrap];
138         if (bootstrapResult) {
139             var histogram = d3.layout.histogram()
140                 .bins(xScale.ticks(100))(bootstrapResult.data);
141             var yBootstrapScale = d3.scale.linear()
142                 .range([size.height/2, 0])
143                 .domain([0, d3.max(histogram, function(d) { return d.y; })]);
144             group = svg.append("g").attr("class", "bootstrap");
145             var bar = group.selectAll(".bar")
146                 .data(histogram)
147                 .enter().append("g")
148                     .attr("class", "bar")
149                     .attr("transform", function(d) { return "translate(" + xScale(d.x) + "," + yBootstrapScale(d.y) + ")"; });
150             bar.append("rect")
151                 .attr("x", 1)
152                 .attr("y", size.height/2)
153                 .attr("width", xScale(histogram[1].x) - xScale(histogram[0].x) - 1)
154                 .attr("height", function(d) { return size.height/2 - yBootstrapScale(d.y); });
155             group = group.append("g").attr("class", "median");
156             this._addRegressionLine(group, xScale, yScale, [[bootstrapResult.median, yMin], [bootstrapResult.median, yMax]], [bootstrapResult.confidenceLow, bootstrapResult.confidenceHigh], true);
157             group.append("circle")
158                 .attr("cx", xScale(bootstrapResult.median))
159                 .attr("cy", yScale(1000/60))
160                 .attr("r", 5);
161         }
162
163         // series
164         group = svg.append("g")
165             .attr("class", "series raw")
166             .selectAll("line")
167                 .data(data[Strings.json.complexity])
168                 .enter();
169         group.append("line")
170             .attr("x1", function(d) { return xScale(d.complexity) - 3; })
171             .attr("x2", function(d) { return xScale(d.complexity) + 3; })
172             .attr("y1", function(d) { return yScale(d.frameLength) - 3; })
173             .attr("y2", function(d) { return yScale(d.frameLength) + 3; });
174         group.append("line")
175             .attr("x1", function(d) { return xScale(d.complexity) - 3; })
176             .attr("x2", function(d) { return xScale(d.complexity) + 3; })
177             .attr("y1", function(d) { return yScale(d.frameLength) + 3; })
178             .attr("y2", function(d) { return yScale(d.frameLength) - 3; });
179
180         group = svg.append("g")
181             .attr("class", "series average")
182             .selectAll("circle")
183                 .data(data[Strings.json.complexityAverage])
184                 .enter();
185         group.append("circle")
186             .attr("cx", function(d) { return xScale(d.complexity); })
187             .attr("cy", function(d) { return yScale(d.frameLength); })
188             .attr("r", 3)
189         group.append("line")
190             .attr("x1", function(d) { return xScale(d.complexity); })
191             .attr("x2", function(d) { return xScale(d.complexity); })
192             .attr("y1", function(d) { return yScale(d.frameLength - d.stdev); })
193             .attr("y2", function(d) { return yScale(d.frameLength + d.stdev); });
194
195         // Cursor
196         var cursorGroup = svg.append("g").attr("class", "cursor hidden");
197         cursorGroup.append("line")
198             .attr("class", "x")
199             .attr("x1", 0)
200             .attr("x2", 0)
201             .attr("y1", yScale(yAxis.scale().domain()[0]) + 10)
202             .attr("y2", yScale(yAxis.scale().domain()[1]));
203         cursorGroup.append("line")
204             .attr("class", "y")
205             .attr("x1", xScale(xAxis.scale().domain()[0]) - 10)
206             .attr("x2", xScale(xAxis.scale().domain()[1]))
207             .attr("y1", 0)
208             .attr("y2", 0)
209         cursorGroup.append("text")
210             .attr("class", "label x")
211             .attr("x", 0)
212             .attr("y", yScale(yAxis.scale().domain()[0]) + 15)
213             .attr("baseline-shift", "-100%")
214             .attr("text-anchor", "middle");
215         cursorGroup.append("text")
216             .attr("class", "label y")
217             .attr("x", xScale(xAxis.scale().domain()[0]) - 15)
218             .attr("y", 0)
219             .attr("baseline-shift", "-30%")
220             .attr("text-anchor", "end");
221         // Area to handle mouse events
222         var area = svg.append("rect")
223             .attr("fill", "transparent")
224             .attr("x", 0)
225             .attr("y", 0)
226             .attr("width", size.width)
227             .attr("height", size.height);
228
229         area.on("mouseover", function() {
230             document.querySelector("#complexity-graph .cursor").classList.remove("hidden");
231         }).on("mouseout", function() {
232             document.querySelector("#complexity-graph .cursor").classList.add("hidden");
233         }).on("mousemove", function() {
234             var location = d3.mouse(this);
235             var location_domain = [xScale.invert(location[0]), yScale.invert(location[1])];
236             cursorGroup.select("line.x")
237                 .attr("x1", location[0])
238                 .attr("x2", location[0]);
239             cursorGroup.select("text.x")
240                 .attr("x", location[0])
241                 .text(location_domain[0].toFixed(1));
242             cursorGroup.select("line.y")
243                 .attr("y1", location[1])
244                 .attr("y2", location[1]);
245             cursorGroup.select("text.y")
246                 .attr("y", location[1])
247                 .text((1000 / location_domain[1]).toFixed(1));
248         });
249     },
250
251     createTimeGraph: function(result, samples, marks, regressions, options, margins, size)
252     {
253         var svg = d3.select("#test-graph-data").append("svg")
254             .attr("id", "time-graph")
255             .attr("width", size.width + margins.left + margins.right)
256             .attr("height", size.height + margins.top + margins.bottom)
257             .append("g")
258                 .attr("transform", "translate(" + margins.left + "," + margins.top + ")");
259
260         // Axis scales
261         var x = d3.scale.linear()
262                 .range([0, size.width])
263                 .domain([
264                     Math.min(d3.min(samples, function(s) { return s.time; }), 0),
265                     d3.max(samples, function(s) { return s.time; })]);
266         var complexityMax = d3.max(samples, function(s) {
267             if (s.time > 0)
268                 return s.complexity;
269             return 0;
270         });
271
272         var yLeft = d3.scale.linear()
273                 .range([size.height, 0])
274                 .domain([0, complexityMax]);
275         var yRight = d3.scale.linear()
276                 .range([size.height, 0])
277                 .domain([1000/20, 1000/60]);
278
279         // Axes
280         var xAxis = d3.svg.axis()
281                 .scale(x)
282                 .orient("bottom")
283                 .tickFormat(function(d) { return (d/1000).toFixed(0); });
284         var yAxisLeft = d3.svg.axis()
285                 .scale(yLeft)
286                 .orient("left");
287         var yAxisRight = d3.svg.axis()
288                 .scale(yRight)
289                 .tickValues([1000/20, 1000/25, 1000/30, 1000/35, 1000/40, 1000/45, 1000/50, 1000/55, 1000/60])
290                 .tickFormat(function(d) { return (1000/d).toFixed(0); })
291                 .orient("right");
292
293         // x-axis
294         svg.append("g")
295             .attr("class", "x axis")
296             .attr("fill", "rgb(235, 235, 235)")
297             .attr("transform", "translate(0," + size.height + ")")
298             .call(xAxis)
299             .append("text")
300                 .attr("class", "label")
301                 .attr("x", size.width)
302                 .attr("y", -6)
303                 .attr("fill", "rgb(235, 235, 235)")
304                 .style("text-anchor", "end")
305                 .text("time");
306
307         // yLeft-axis
308         svg.append("g")
309             .attr("class", "yLeft axis")
310             .attr("fill", "#7ADD49")
311             .call(yAxisLeft)
312             .append("text")
313                 .attr("class", "label")
314                 .attr("transform", "rotate(-90)")
315                 .attr("y", 6)
316                 .attr("fill", "#7ADD49")
317                 .attr("dy", ".71em")
318                 .style("text-anchor", "end")
319                 .text(Strings.text.complexity);
320
321         // yRight-axis
322         svg.append("g")
323             .attr("class", "yRight axis")
324             .attr("fill", "#FA4925")
325             .attr("transform", "translate(" + size.width + ", 0)")
326             .call(yAxisRight)
327             .append("text")
328                 .attr("class", "label")
329                 .attr("x", 9)
330                 .attr("y", -20)
331                 .attr("fill", "#FA4925")
332                 .attr("dy", ".71em")
333                 .style("text-anchor", "start")
334                 .text(Strings.text.frameRate);
335
336         // marks
337         var yMin = yRight(yAxisRight.scale().domain()[0]);
338         var yMax = yRight(yAxisRight.scale().domain()[1]);
339         for (var markName in marks) {
340             var mark = marks[markName];
341             var xLocation = x(mark.time);
342
343             var markerGroup = svg.append("g")
344                 .attr("class", "marker")
345                 .attr("transform", "translate(" + xLocation + ", 0)");
346             markerGroup.append("text")
347                 .attr("transform", "translate(10, " + (yMin - 10) + ") rotate(-90)")
348                 .style("text-anchor", "start")
349                 .text(markName)
350             markerGroup.append("line")
351                 .attr("x1", 0)
352                 .attr("x2", 0)
353                 .attr("y1", yMin)
354                 .attr("y2", yMax);
355         }
356
357         if (Strings.json.controller in result) {
358             var complexity = result[Strings.json.controller];
359             var regression = svg.append("g")
360                 .attr("class", "complexity mean");
361             this._addRegressionLine(regression, x, yLeft, [[samples[0].time, complexity.average], [samples[samples.length - 1].time, complexity.average]], complexity.stdev);
362         }
363         if (Strings.json.frameLength in result) {
364             var frameLength = result[Strings.json.frameLength];
365             var regression = svg.append("g")
366                 .attr("class", "fps mean");
367             this._addRegressionLine(regression, x, yRight, [[samples[0].time, 1000/frameLength.average], [samples[samples.length - 1].time, 1000/frameLength.average]], frameLength.stdev);
368         }
369
370         // right-target
371         if (options["adjustment"] == "adaptive") {
372             var targetFrameLength = 1000 / options["frame-rate"];
373             svg.append("line")
374                 .attr("x1", x(0))
375                 .attr("x2", size.width)
376                 .attr("y1", yRight(targetFrameLength))
377                 .attr("y2", yRight(targetFrameLength))
378                 .attr("class", "target-fps marker");
379         }
380
381         // Cursor
382         var cursorGroup = svg.append("g").attr("class", "cursor");
383         cursorGroup.append("line")
384             .attr("x1", 0)
385             .attr("x2", 0)
386             .attr("y1", yMin)
387             .attr("y2", yMin);
388
389         // Data
390         var allData = samples;
391         var filteredData = samples.filter(function (sample) {
392             return "smoothedFrameLength" in sample;
393         });
394
395         function addData(name, data, yCoordinateCallback, pointRadius, omitLine) {
396             var svgGroup = svg.append("g").attr("id", name);
397             if (!omitLine) {
398                 svgGroup.append("path")
399                     .datum(data)
400                     .attr("d", d3.svg.line()
401                         .x(function(d) { return x(d.time); })
402                         .y(yCoordinateCallback));
403             }
404             svgGroup.selectAll("circle")
405                 .data(data)
406                 .enter()
407                 .append("circle")
408                 .attr("cx", function(d) { return x(d.time); })
409                 .attr("cy", yCoordinateCallback)
410                 .attr("r", pointRadius);
411
412             cursorGroup.append("circle")
413                 .attr("class", name)
414                 .attr("r", pointRadius + 2);
415         }
416
417         addData("complexity", allData, function(d) { return yLeft(d.complexity); }, 2);
418         addData("rawFPS", allData, function(d) { return yRight(d.frameLength); }, 1);
419         addData("filteredFPS", filteredData, function(d) { return yRight(d.smoothedFrameLength); }, 2);
420
421         // regressions
422         var regressionGroup = svg.append("g")
423             .attr("id", "regressions");
424         if (regressions) {
425             var complexities = [];
426             regressions.forEach(function (regression) {
427                 if (!isNaN(regression.segment1[0][1]) && !isNaN(regression.segment1[1][1])) {
428                     regressionGroup.append("line")
429                         .attr("x1", x(regression.segment1[0][0]))
430                         .attr("x2", x(regression.segment1[1][0]))
431                         .attr("y1", yRight(regression.segment1[0][1]))
432                         .attr("y2", yRight(regression.segment1[1][1]));
433                 }
434                 if (!isNaN(regression.segment2[0][1]) && !isNaN(regression.segment2[1][1])) {
435                     regressionGroup.append("line")
436                         .attr("x1", x(regression.segment2[0][0]))
437                         .attr("x2", x(regression.segment2[1][0]))
438                         .attr("y1", yRight(regression.segment2[0][1]))
439                         .attr("y2", yRight(regression.segment2[1][1]));
440                 }
441                 // inflection point
442                 regressionGroup.append("circle")
443                     .attr("cx", x(regression.segment1[1][0]))
444                     .attr("cy", yLeft(regression.complexity))
445                     .attr("r", 5);
446                 complexities.push(regression.complexity);
447             });
448             if (complexities.length) {
449                 var yLeftComplexities = d3.svg.axis()
450                     .scale(yLeft)
451                     .tickValues(complexities)
452                     .tickSize(10)
453                     .orient("left");
454                 svg.append("g")
455                     .attr("class", "complexity yLeft axis")
456                     .call(yLeftComplexities);
457             }
458         }
459
460         // Area to handle mouse events
461         var area = svg.append("rect")
462             .attr("fill", "transparent")
463             .attr("x", 0)
464             .attr("y", 0)
465             .attr("width", size.width)
466             .attr("height", size.height);
467
468         var timeBisect = d3.bisector(function(d) { return d.time; }).right;
469         var statsToHighlight = ["complexity", "rawFPS", "filteredFPS"];
470         area.on("mouseover", function() {
471             document.querySelector("#time-graph .cursor").classList.remove("hidden");
472             document.querySelector("#test-graph nav").classList.remove("hide-data");
473         }).on("mouseout", function() {
474             document.querySelector("#time-graph .cursor").classList.add("hidden");
475             document.querySelector("#test-graph nav").classList.add("hide-data");
476         }).on("mousemove", function() {
477             var form = document.forms["time-graph-options"].elements;
478
479             var mx_domain = x.invert(d3.mouse(this)[0]);
480             var index = Math.min(timeBisect(allData, mx_domain), allData.length - 1);
481             var data = allData[index];
482             var cursor_x = x(data.time);
483             var cursor_y = yAxisRight.scale().domain()[1];
484             var ys = [yRight(yAxisRight.scale().domain()[0]), yRight(yAxisRight.scale().domain()[1])];
485
486             document.querySelector("#test-graph nav .time").textContent = (data.time / 1000).toFixed(4) + "s (" + index + ")";
487             statsToHighlight.forEach(function(name) {
488                 var element = document.querySelector("#test-graph nav ." + name);
489                 var content = "";
490                 var data_y = null;
491                 switch (name) {
492                 case "complexity":
493                     content = data.complexity;
494                     data_y = yLeft(data.complexity);
495                     break;
496                 case "rawFPS":
497                     content = (1000/data.frameLength).toFixed(2);
498                     data_y = yRight(data.frameLength);
499                     break;
500                 case "filteredFPS":
501                     if ("smoothedFrameLength" in data) {
502                         content = (1000/data.smoothedFrameLength).toFixed(2);
503                         data_y = yRight(data.smoothedFrameLength);
504                     }
505                     break;
506                 }
507
508                 element.textContent = content;
509
510                 if (form[name].checked && data_y !== null) {
511                     ys.push(data_y);
512                     cursorGroup.select("." + name)
513                         .attr("cx", cursor_x)
514                         .attr("cy", data_y);
515                     document.querySelector("#time-graph .cursor ." + name).classList.remove("hidden");
516                 } else
517                     document.querySelector("#time-graph .cursor ." + name).classList.add("hidden");
518             });
519
520             if (form["rawFPS"].checked)
521                 cursor_y = Math.max(cursor_y, data.frameLength);
522             cursorGroup.select("line")
523                 .attr("x1", cursor_x)
524                 .attr("x2", cursor_x)
525                 .attr("y1", Math.min.apply(null, ys))
526                 .attr("y2", Math.max.apply(null, ys));
527
528         });
529     },
530
531     _showOrHideNodes: function(isShown, selector) {
532         var nodeList = document.querySelectorAll(selector);
533         if (isShown) {
534             for (var i = 0; i < nodeList.length; ++i)
535                 nodeList[i].classList.remove("hidden");
536         } else {
537             for (var i = 0; i < nodeList.length; ++i)
538                 nodeList[i].classList.add("hidden");
539         }
540     },
541
542     onComplexityGraphOptionsChanged: function() {
543         var form = document.forms["complexity-graph-options"].elements;
544         benchmarkController._showOrHideNodes(form["series-raw"].checked, "#complexity-graph .series.raw");
545         benchmarkController._showOrHideNodes(form["series-average"].checked, "#complexity-graph .series.average");
546         benchmarkController._showOrHideNodes(form["regression-time-score"].checked, "#complexity-graph .mean.complexity");
547         benchmarkController._showOrHideNodes(form["bootstrap-score"].checked, "#complexity-graph .bootstrap");
548         benchmarkController._showOrHideNodes(form["complexity-regression-aggregate-raw"].checked, "#complexity-graph .regression.raw");
549         benchmarkController._showOrHideNodes(form["complexity-regression-aggregate-average"].checked, "#complexity-graph .regression.average");
550     },
551
552     onTimeGraphOptionsChanged: function() {
553         var form = document.forms["time-graph-options"].elements;
554         benchmarkController._showOrHideNodes(form["markers"].checked, ".marker");
555         benchmarkController._showOrHideNodes(form["averages"].checked, "#test-graph-data .mean");
556         benchmarkController._showOrHideNodes(form["complexity"].checked, "#complexity");
557         benchmarkController._showOrHideNodes(form["rawFPS"].checked, "#rawFPS");
558         benchmarkController._showOrHideNodes(form["filteredFPS"].checked, "#filteredFPS");
559         benchmarkController._showOrHideNodes(form["regressions"].checked, "#regressions");
560     },
561
562     onGraphTypeChanged: function() {
563         var form = document.forms["graph-type"].elements;
564         var testResult = document.getElementById("test-graph-data")._testResult;
565         var isTimeSelected = form["graph-type"].value == "time";
566
567         benchmarkController._showOrHideNodes(isTimeSelected, "#time-graph");
568         benchmarkController._showOrHideNodes(isTimeSelected, "form[name=time-graph-options]");
569         benchmarkController._showOrHideNodes(!isTimeSelected, "#complexity-graph");
570         benchmarkController._showOrHideNodes(!isTimeSelected, "form[name=complexity-graph-options]");
571
572         var score, mean;
573         if (isTimeSelected) {
574             score = testResult[Strings.json.score].toFixed(2);
575
576             var regression = testResult[Strings.json.controller];
577             mean = [
578                 "mean: ",
579                 regression.average.toFixed(2),
580                 " ± ",
581                 regression.stdev.toFixed(2),
582                 " (",
583                 regression.percent.toFixed(2),
584                 "%)"];
585             if (regression.concern) {
586                 mean = mean.concat([
587                     ", worst 5%: ",
588                     regression.concern.toFixed(2)]);
589             }
590             mean = mean.join("");
591         } else {
592             var complexityRegression = testResult[Strings.json.complexity];
593             var complexityAverageRegression = testResult[Strings.json.complexityAverage];
594
595             document.getElementById("complexity-regression-aggregate-raw").textContent = complexityRegression.complexity.toFixed(2) + ", ±" + complexityRegression.stdev.toFixed(2) + "ms";
596             document.getElementById("complexity-regression-aggregate-average").textContent = complexityAverageRegression.complexity.toFixed(2) + ", ±" + complexityAverageRegression.stdev.toFixed(2) + "ms";
597
598             var bootstrap = complexityRegression[Strings.json.bootstrap];
599             score = bootstrap.median.toFixed(2);
600             mean = [
601                 "95% CI: ",
602                 bootstrap.confidenceLow.toFixed(2),
603                 "–",
604                 bootstrap.confidenceHigh.toFixed(2)
605             ].join("");
606         }
607
608         sectionsManager.setSectionScore("test-graph", score, mean);
609     }
610 });