Skip to content

Instantly share code, notes, and snippets.

@burg
Created June 9, 2015 21:57
Show Gist options
  • Save burg/2df5eb5e7d6f745b31a3 to your computer and use it in GitHub Desktop.
Save burg/2df5eb5e7d6f745b31a3 to your computer and use it in GitHub Desktop.
Rendering an interactive graph with d3
render: function()
{
if (this._requestAnimationFrameToken)
this._requestAnimationFrameToken = undefined;
var runs = this._results.runs;
var availWidth = 780;
var padding = 2;
var width = availWidth - 2 * padding;
var totalWidth = width;
var totalHeight = 72;
var gutterHeight = 16;
var graphHeight = totalHeight - (2 * gutterHeight);
var maxDuration = this.maxDuration;
var missingResultCount = this._aggregates.missingResultCount;
// For rect fills, don't round because it can cause see-through gaps.
// It's more acceptable to have some blurred edges. For lines, always
// use the rounded version otherwise everything will be a smudgy mess.
var x = d3.scale.linear()
.domain([0, runs.length])
.range([0, width]);
var roundX = x.copy()
.rangeRound(x.range());
var y = d3.scale.linear()
.domain([0, maxDuration])
.rangeRound([1, graphHeight - 1]);
var roundY = y.copy()
.rangeRound(y.range());
if (!this.svg) {
this.svg = d3.select(this.element).append("svg")
.attr("width", totalWidth)
.attr("height", totalHeight);
}
var svg = this.svg;
svg.selectAll(".repeat-block")
.data(this._aggregates.repeatData).enter()
.append("rect")
.attr("class", function(d) { return "repeat-block " + d.outcome; })
.attr("x", function(d) { return x(d.begin); })
.attr("width", function(d) { return x(d.repeat); })
.attr("y", 1 + gutterHeight + roundY(0))
.attr("height", roundY(maxDuration));
console.log(this._timingData);
svg.selectAll(".repeat-lines")
.data(this._aggregates.timingData).enter()
.append("line")
.attr("class", function(d) { return "repeat-lines " + d.outcome; })
.attr("x1", function(d) { return roundX(d.begin + missingResultCount); })
.attr("y1", function(d) { return 1 + gutterHeight + roundY(maxDuration - d.duration); })
.attr("x2", function(d) { return roundX(d.begin + d.repeat + missingResultCount); })
.attr("y2", function(d) { return 1 + gutterHeight + roundY(maxDuration - d.duration); });
var circleRadius = 3;
svg.selectAll(".critical-bubbles")
.data(this._aggregates.repeatData).enter()
.append("circle")
.attr("class", function(d) { return "critical-bubbles " + d.outcome; })
.attr("cx", function(d) { return roundX(d.begin) + circleRadius; })
.attr("cy", 1 + gutterHeight / 2)
.attr("r", circleRadius);
var widget = this;
function textLabelForRun(run) {
switch (run.data.outcome) {
case WK.TestResult.Outcome.Pass:
return run.data.duration + "s";
case WK.TestResult.Outcome.FailText:
case WK.TestResult.Outcome.FailImage:
case WK.TestResult.Outcome.FailAudio:
return "FAIL";
case WK.TestResult.Outcome.Timeout:
return "TIMEOUT";
case WK.TestResult.Outcome.Crash:
return "CRASH";
case WK.TestResult.Outcome.Skip:
case WK.TestResult.Outcome.Missing:
case WK.TestResult.Outcome.NoData:
default:
return "UNKNOWN";
}
}
// Find our repeat data for this run.
var timingData = this._aggregates.timingData;
var selectedRunsData = _.map(this.selectedRuns, function(runOrdinal) {
for (var i = 0; i < timingData.length; ++i) {
if (timingData[i].begin + timingData[i].repeat > runOrdinal) {
return {ordinal: runOrdinal, data: timingData[i]};
}
}
return null;
}, this);
function keyForRunData(run) { return run.ordinal; }
var overlay = svg.selectAll(".selection-overlay").data(selectedRunsData, keyForRunData);
overlay.enter()
.append("rect")
.attr("class", function(d) { return "selection-overlay " + d.data.outcome; })
.attr("opacity", 1)
.attr("x", function(d) { return roundX(d.ordinal); })
.attr("y", 1 + gutterHeight + roundY(0))
.attr("width", x(1))
.attr("height", roundY(maxDuration))
overlay.exit()
.remove();
var label = svg.selectAll(".selection-text").data(selectedRunsData, keyForRunData);
label.enter()
.append("text")
.attr("class", function(d) { return "selection-text " + d.data.outcome; })
.attr("opacity", 1)
.attr("x", function(d) { return roundX(d.ordinal + 0.5); })
.attr("y", 1 + gutterHeight + roundY(maxDuration) + gutterHeight)
.attr("height", gutterHeight)
.attr("text-anchor", "middle")
.text(textLabelForRun)
label.exit()
.remove();
function mouseleave() {
this.dispatchEventToListeners(WK.TestResultHistoryGraphView.Event.RunSelectionChanged, {ordinals: []});
}
svg
.on("mouseleave", mouseleave.bind(this))
.on("mousemove", function() {
var mouseX = d3.mouse(this)[0];
if (mouseX < x.range()[0] || mouseX > x.range()[1]) {
mouseleave.call(this);
return;
}
var runOrdinal = Math.floor(x.invert(mouseX));
widget.dispatchEventToListeners(WK.TestResultHistoryGraphView.Event.RunSelectionChanged, {ordinals: [runOrdinal]});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment