Created
June 9, 2015 21:57
-
-
Save burg/2df5eb5e7d6f745b31a3 to your computer and use it in GitHub Desktop.
Rendering an interactive graph with d3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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