Skip to content

Commit 848adf3

Browse files
daniloafstr4d
authored andcommitted
Implement error bars using standard deviation on timeline plots
jqplot.ohlcRendererWithErrorBars.min.js is the standard OHLC Renderer plugin from jqPlot 1.0.9 (revision d96a669) with the error-bar addition given here: https://bitbucket.org/cleonello/jqplot/issues/35/add-error-bar-capability#comment-141580
1 parent a85b782 commit 848adf3

File tree

5 files changed

+68
-9
lines changed

5 files changed

+68
-9
lines changed

codespeed/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@
6666
# COMP_EXECUTABLES = [
6767
# ('myexe', '21df2423ra'),
6868
# ('myexe', 'L'),]
69+
70+
USE_ERROR_BARS = True # True to enable error bars on Timeline view

codespeed/static/js/jqplot/jqplot.ohlcRendererWithErrorBars.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codespeed/static/js/timeline.js

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ function shouldPlotEquidistant() {
2828
return $("#equidistant").is(':checked');
2929
}
3030

31+
function shouldPlotErrorBars() {
32+
return $("#show_error_bars").is(':checked');
33+
}
34+
3135
function getConfiguration() {
3236
var config = {
3337
exe: readCheckbox("input[name='executable']:checked"),
3438
base: $("#baseline option:selected").val(),
3539
ben: $("input[name='benchmark']:checked").val(),
3640
env: $("input[name='environments']:checked").val(),
3741
revs: $("#revisions option:selected").val(),
38-
equid: $("#equidistant").is(':checked') ? "on" : "off"
42+
equid: $("#equidistant").is(':checked') ? "on" : "off",
43+
error: $("#show_error_bars").is(':checked') ? "on" : "off"
3944
};
4045

4146
var branch = readCheckbox("input[name='branch']:checked");
@@ -64,17 +69,41 @@ function OnMarkerClickHandler(ev, gridpos, datapos, neighbor, plot) {
6469
function renderPlot(data) {
6570
var plotdata = [],
6671
series = [],
72+
firstdates = [],
73+
lastdates = [],
6774
lastvalues = [];//hopefully the smallest values for determining significant digits.
6875
seriesindex = [];
76+
var errorSeries = 0;
6977
for (var branch in data.branches) {
7078
// NOTE: Currently, only the "default" branch is shown in the timeline
7179
for (var exe_id in data.branches[branch]) {
80+
if (shouldPlotErrorBars()) {
81+
marker = false;
82+
var error = new Array();
83+
for (res in data["branches"][branch][exe_id]) {
84+
var date = data["branches"][branch][exe_id][res][0];
85+
var value = data["branches"][branch][exe_id][res][1];
86+
var std_dev = data["branches"][branch][exe_id][res][2];
87+
error.push([date, value - std_dev, value + std_dev, data["branches"][branch][exe_id][res][3]]);
88+
}
89+
plotdata.push(error);
90+
series.push({renderer:$.jqplot.OHLCRenderer, rendererOptions:{errorBar:true}, showLabel: false, showMarker: true,
91+
"label": $("label[for*='executable" + getColor(exe_id) + "']").html() + " error", color: "#C0C0C0"});
92+
errorSeries++;
93+
}
7294
// FIXME if (branch !== "default") { label += " - " + branch; }
7395
var label = $("label[for*='executable" + exe_id + "']").html();
7496
series.push({"label": label, "color": getColor(exe_id)});
7597
seriesindex.push(exe_id);
76-
plotdata.push(data.branches[branch][exe_id]);
77-
lastvalues.push(data.branches[branch][exe_id][0][1]);
98+
var exeData = data.branches[branch][exe_id];
99+
plotdata.push(exeData);
100+
var startDate = new Date(exeData[exeData.length - 1][0])
101+
var endDate = new Date(exeData[0][0]);
102+
startDate.setDate(startDate.getDate() - 1);
103+
endDate.setDate(endDate.getDate() + 1);
104+
firstdates.push(startDate);
105+
lastdates.push(endDate);
106+
lastvalues.push(exeData[0][1]);
78107
}
79108
//determine significant digits
80109
var digits = 2;
@@ -115,7 +144,8 @@ function renderPlot(data) {
115144
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
116145
tickOptions:{formatString:'%b %d'},
117146
pad: 1.01,
118-
autoscale:true,
147+
min: Math.min.apply(Math, firstdates),
148+
max: Math.max.apply(Math, lastdates),
119149
rendererOptions:{sortMergedLabels:true} /* only relevant when
120150
$.jqplot.CategoryAxisRenderer is used */
121151
}
@@ -129,7 +159,7 @@ function renderPlot(data) {
129159
},
130160
cursor:{show:true, zoom:true, showTooltip:false, clickReset:true}
131161
};
132-
if (series.length > 4) {
162+
if (series.length > 4 + errorSeries) {
133163
// Move legend outside plot area to unclutter
134164
var labels = [];
135165
for (var l in series) {
@@ -149,14 +179,23 @@ function renderPlot(data) {
149179

150180
function renderMiniplot(plotid, data) {
151181
var plotdata = [],
152-
series = [];
182+
series = [],
183+
firstdates = [],
184+
lastdates = [];
153185

154186
for (var branch in data.branches) {
155187
for (var id in data.branches[branch]) {
156188
series.push({
157189
"label": $("label[for*='executable" + id + "']").html(),
158190
"color": getColor(id)
159191
});
192+
var exeData = data.branches[branch][id];
193+
var startDate = new Date(exeData[exeData.length - 1][0])
194+
var endDate = new Date(exeData[0][0]);
195+
startDate.setDate(startDate.getDate() - 1);
196+
endDate.setDate(endDate.getDate() + 1);
197+
firstdates.push(startDate);
198+
lastdates.push(endDate);
160199
plotdata.push(data.branches[branch][id]);
161200
}
162201
}
@@ -181,7 +220,10 @@ function renderMiniplot(plotid, data) {
181220
renderer:$.jqplot.DateAxisRenderer,
182221
pad: 1.01,
183222
autoscale:true,
184-
showTicks: false
223+
showTicks: false,
224+
min: Math.min.apply(Math, firstdates),
225+
max: Math.max.apply(Math, lastdates),
226+
rendererOptions:{sortMergedLabels:true}
185227
}
186228
},
187229
highlighter: {show:false},
@@ -193,6 +235,7 @@ function renderMiniplot(plotid, data) {
193235
function render(data) {
194236
$("#revisions").attr("disabled", false);
195237
$("#equidistant").attr("disabled", false);
238+
$("#show_error_bars").attr("disabled", false);
196239
$("#plotgrid").html("");
197240
if(data.error !== "None") {
198241
var h = $("#content").height();//get height for error message
@@ -208,6 +251,7 @@ function render(data) {
208251
//Render Grid of plots
209252
$("#revisions").attr("disabled",true);
210253
$("#equidistant").attr("disabled", true);
254+
$("#show_error_bars").attr("disabled",true);
211255
for (var bench in data.timelines) {
212256
var plotid = "plot_" + data.timelines[bench].benchmark_id;
213257
$("#plotgrid").append('<div id="' + plotid + '" class="miniplot"></div>');
@@ -254,6 +298,7 @@ function initializeSite(event) {
254298
$("input[name='benchmark']" ).change(updateUrl);
255299
$("input[name='environments']").change(updateUrl);
256300
$("#equidistant" ).change(updateUrl);
301+
$("#show_error_bars" ).change(updateUrl);
257302
}
258303

259304
function refreshSite(event) {
@@ -307,6 +352,7 @@ function setValuesOfInputFields(event) {
307352

308353
$("#baselinecolor").css("background-color", baselineColor);
309354
$("#equidistant").prop('checked', valueOrDefault(event.parameters.equid, defaults.equidistant) === "on");
355+
$("#show_error_bars").prop('checked', valueOrDefault(event.parameters.error, defaults.error) === "on");
310356
}
311357

312358
function init(def) {

codespeed/templates/codespeed/timeline.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@
8383
<input id="equidistant" name="equidistant" type="checkbox" />
8484
<label for="equidistant">Equidistant</label>
8585
</span>
86+
{% if use_error_bars %}
87+
<span class="options" title="Shows error bars in the plots">
88+
<input id="show_error_bars" type="checkbox" name="show_error_bars" checked="checked"/>
89+
<label for="show_error_bars">Show error bars</label>
90+
</span>
91+
{% endif %}
8692
<a id="permalink" href="#">Permalink</a>
8793
</div>
8894
<div id="content" class="clearfix">
@@ -102,6 +108,7 @@
102108
<script type="text/javascript" src="{{ STATIC_URL }}js/jqplot/jqplot.categoryAxisRenderer.min.js"></script>
103109
<script type="text/javascript" src="{{ STATIC_URL }}js/jqplot/jqplot.canvasTextRenderer.min.js"></script>
104110
<script type="text/javascript" src="{{ STATIC_URL }}js/jqplot/jqplot.canvasAxisLabelRenderer.min.js"></script>
111+
<script type="text/javascript" src="{{ STATIC_URL }}js/jqplot/jqplot.ohlcRendererWithErrorBars.min.js"></script>
105112
<script type="text/javascript">
106113
var CHANGES_URL = "{% url "changes" %}";
107114
</script>
@@ -115,7 +122,8 @@
115122
branches: [{% for b in branch_list %}"{{ branch }}", {% endfor %}],
116123
benchmark: "{{ defaultbenchmark }}",
117124
environment: {{ defaultenvironment.id }},
118-
equidistant: "{{ defaultequid }}"
125+
equidistant: "{{ defaultequid }}",
126+
error: "on"
119127
});
120128
});
121129
</script>

codespeed/views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ def timeline(request):
429429
executables = {}
430430
for proj in Project.objects.filter(track=True):
431431
executables[proj] = Executable.objects.filter(project=proj)
432+
use_error_bars = hasattr(settings, 'USE_ERROR_BARS') and settings.USE_ERROR_BARS
432433
return render_to_response('codespeed/timeline.html', {
433434
'checkedexecutables': checkedexecutables,
434435
'defaultbaseline': defaultbaseline,
@@ -442,7 +443,8 @@ def timeline(request):
442443
'environments': enviros,
443444
'branch_list': branch_list,
444445
'defaultbranch': defaultbranch,
445-
'defaultequid': defaultequid
446+
'defaultequid': defaultequid,
447+
'use_error_bars': use_error_bars
446448
}, context_instance=RequestContext(request))
447449

448450

0 commit comments

Comments
 (0)