implement mini view and add info panels to page view
This commit is contained in:
parent
7aef393008
commit
35b759287c
|
@ -20,18 +20,16 @@ var parseWorker = function(tags) {
|
|||
return null;
|
||||
};
|
||||
|
||||
var initTimeline = function(data, timeExtents) {
|
||||
var initTimeline = function(options, data, timeExtents) {
|
||||
"use strict";
|
||||
|
||||
console.log("extents:", timeExtents);
|
||||
|
||||
// http://bl.ocks.org/bunkat/2338034
|
||||
var margin = { top: 10, right: 10, bottom: 100, left: 80 };
|
||||
var width = 960 - margin.left - margin.right;
|
||||
var height = 500 - margin.top - margin.bottom;
|
||||
var margin = { top: 10, right: 10, bottom: 10, left: 80 };
|
||||
var width = 800 - margin.left - margin.right;
|
||||
var height = 350 - margin.top - margin.bottom;
|
||||
|
||||
var miniHeight = data.length * 12 + 50;
|
||||
var mainHeight = height - miniHeight - 50;
|
||||
var miniHeight = data.length * 12 + 30;
|
||||
var mainHeight = height - miniHeight - 10;
|
||||
|
||||
var x = d3.time.scale()
|
||||
.range([0, width])
|
||||
|
@ -46,7 +44,7 @@ var initTimeline = function(data, timeExtents) {
|
|||
.domain([0, data.length])
|
||||
.range([0, miniHeight]);
|
||||
|
||||
var chart = d3.select("#timeline-container")
|
||||
var chart = d3.select(options.container)
|
||||
.append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
|
@ -78,22 +76,32 @@ var initTimeline = function(data, timeExtents) {
|
|||
.attr("height", mainHeight)
|
||||
.attr("class", "mini");
|
||||
|
||||
var miniGroups = mini.append("g");
|
||||
|
||||
// performance hack: performance in Firefox as of 39.0 is poor due to some
|
||||
// d3 bugs
|
||||
// Limit the initial selection to ~1/6th of the total to make things
|
||||
// bearable (user can still increase as desired)
|
||||
var start = timeExtents[0];
|
||||
var end = timeExtents[1];
|
||||
var reducedEnd = new Date(start.getTime() + ((end - start) / 8));
|
||||
|
||||
var brush = d3.svg.brush()
|
||||
.x(x)
|
||||
.extent(timeExtents);
|
||||
.extent([start, reducedEnd]);
|
||||
|
||||
function updateLanes() {
|
||||
var lines = laneLines.selectAll(".laneLine")
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
lines.enter().append("line")
|
||||
.attr("x1", margin.right)
|
||||
.attr("x1", 0)
|
||||
.attr("x2", width)
|
||||
.attr("stroke", "lightgray")
|
||||
.attr("class", "laneLine");
|
||||
|
||||
lines.attr("y1", function(d, i) { return y1(i); })
|
||||
.attr("y2", function(d, i) { return y1(i); });
|
||||
lines.attr("y1", function(d, i) { return y1(i - 0.1); })
|
||||
.attr("y2", function(d, i) { return y1(i - 0.1); });
|
||||
|
||||
lines.exit().remove();
|
||||
|
||||
|
@ -145,25 +153,42 @@ var initTimeline = function(data, timeExtents) {
|
|||
.attr("width", function(d) {
|
||||
return x1(d.end_date) - x1(d.start_date);
|
||||
})
|
||||
.attr("fill", function(d) { return statusColorMap[d.status]; });
|
||||
.attr("fill", function(d) { return statusColorMap[d.status]; })
|
||||
.on("mouseover", options.onMouseover)
|
||||
.on("mouseout", options.onMouseout)
|
||||
.on("click", options.onClick);
|
||||
|
||||
rects.exit().remove();
|
||||
groups.exit().remove();
|
||||
}
|
||||
|
||||
function updateMiniItems() {
|
||||
var groups = miniGroups.selectAll("g")
|
||||
.data(data, function(d) { return d.key; });
|
||||
|
||||
groups.enter().append("g");
|
||||
|
||||
var rects = groups.selectAll("rect").data(
|
||||
function(d) { return d.values; },
|
||||
function(d) { return d.name; });
|
||||
|
||||
rects.enter().append("rect")
|
||||
.attr("x", function(d) { return x(d.start_date); })
|
||||
.attr("y", function(d) { return y2(parseWorker(d.tags) + 0.5) - 5; })
|
||||
.attr("width", function(d) { return x(d.end_date) - x(d.start_date); })
|
||||
.attr("height", 10);
|
||||
|
||||
rects.exit().remove();
|
||||
groups.exit().remove();
|
||||
}
|
||||
|
||||
function update() {
|
||||
// TODO: move all of the data enter() blocks into update() ?
|
||||
// the example seems to be relatively non-idiotmatic d3
|
||||
|
||||
//mini.select(".brush").call(brush.extent(brush.extent())); // ???
|
||||
|
||||
x1.domain(brush.extent());
|
||||
|
||||
updateLanes();
|
||||
updateItems();
|
||||
}
|
||||
|
||||
|
||||
brush.on("brush", update);
|
||||
|
||||
mini.append("g")
|
||||
|
@ -171,12 +196,15 @@ var initTimeline = function(data, timeExtents) {
|
|||
.call(brush)
|
||||
.selectAll("rect")
|
||||
.attr("y", 1)
|
||||
.attr("height", miniHeight - 1);
|
||||
.attr("height", miniHeight - 1)
|
||||
.attr("fill", "dodgerblue")
|
||||
.attr("fill-opacity", 0.365);
|
||||
|
||||
updateMiniItems();
|
||||
update();
|
||||
};
|
||||
|
||||
function loadTimeline(path) { // eslint-disable-line no-unused-vars
|
||||
function loadTimeline(path, options) { // eslint-disable-line no-unused-vars
|
||||
"use strict";
|
||||
|
||||
d3.json(path, function(error, data) {
|
||||
|
@ -200,14 +228,15 @@ function loadTimeline(path) { // eslint-disable-line no-unused-vars
|
|||
/*eslint-enable camelcase*/
|
||||
});
|
||||
|
||||
data = data.filter(function (d) { return d.duration > 0; });
|
||||
|
||||
var nested = d3.nest()
|
||||
.key(function(d) { return parseWorker(d.tags); })
|
||||
.sortKeys(d3.ascending)
|
||||
.entries(data);
|
||||
|
||||
console.log(nested);
|
||||
window.nested = nested;
|
||||
|
||||
initTimeline(nested, [ minStart, maxEnd ]);
|
||||
initTimeline(options, nested, [ minStart, maxEnd ]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,6 +3,25 @@
|
|||
|
||||
{% block title %}Tempest: Execution Timeline (run #{{run_id}}){% endblock %}
|
||||
|
||||
{% block head-extra %}
|
||||
<style>
|
||||
#timeline-info table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
word-wrap: break-word
|
||||
}
|
||||
|
||||
#timeline-info table td:nth-child(1) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
#timeline-log pre {
|
||||
overflow-x: scroll;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
|
@ -11,7 +30,7 @@
|
|||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-8">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-clock-o fa-fw"></i> Timeline
|
||||
|
@ -22,13 +41,179 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-info fa-fw"></i> Info
|
||||
</div>
|
||||
|
||||
<div id="timeline-info" class="panel-body">
|
||||
<em>Mouse over an item to view info.</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-file-text fa-fw"></i> Log Output
|
||||
</div>
|
||||
|
||||
<div id="timeline-details" class="panel-body">
|
||||
<em>Click an item to view log details.</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'js/timeline.js' %}"></script>
|
||||
<script>
|
||||
var originalInfoContent = null;
|
||||
var originalDetailsContent = null;
|
||||
|
||||
var showInfo = function(item) {
|
||||
var parent = $("#timeline-info");
|
||||
if (originalInfoContent === null) {
|
||||
originalInfoContent = parent.html();
|
||||
}
|
||||
|
||||
var e = $("<table>", {
|
||||
"class": 'table table-bordered table-hover table-striped'
|
||||
});
|
||||
|
||||
var nameParts = item.name.split(".");
|
||||
var pkg = nameParts.slice(0, nameParts.length - 2).join('.');
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Name' }))
|
||||
.append($("<td>", { text: nameParts[nameParts.length - 1] })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Class' }))
|
||||
.append($("<td>", { text: nameParts[nameParts.length - 2] })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Module' }))
|
||||
.append($("<td>", { text: pkg })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Status' }))
|
||||
.append($("<td>", { text: item.status })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Tags' }))
|
||||
.append($("<td>", { text: item.tags.join(", ") })));
|
||||
|
||||
e.append($("<tr>")
|
||||
.append($("<td>", { text: 'Duration' }))
|
||||
.append($("<td>", { text: item.duration + " seconds" })));
|
||||
|
||||
parent.empty();
|
||||
e.appendTo(parent);
|
||||
};
|
||||
|
||||
var hideInfo = function() {
|
||||
$("#timeline-info").html(originalInfoContent);
|
||||
};
|
||||
|
||||
var showDetails = function(item) {
|
||||
var parent = $("#timeline-details");
|
||||
|
||||
showInfo(item);
|
||||
|
||||
d3.json("/tempest/api/details/{{run_id}}/" + item.name, function(error, data) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (originalDetailsContent === null) {
|
||||
originalDetailsContent = parent.html();
|
||||
}
|
||||
|
||||
parent.empty();
|
||||
for (var prop in data) {
|
||||
$("<h3>").text(prop).appendTo(parent);
|
||||
$("<pre>").text(data[prop]).appendTo(parent);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
var hideDetails = function() {
|
||||
$("#timeline-details").html(originalDetailsContent);
|
||||
};
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
loadTimeline("/tempest/api/raw/{{run_id}}/");
|
||||
var selectedItem = null;
|
||||
var selectedValue = null;
|
||||
|
||||
loadTimeline("/tempest/api/raw/{{run_id}}/", {
|
||||
container: $("#timeline-container")[0],
|
||||
onClick: function(d) {
|
||||
var self = d3.select(this);
|
||||
|
||||
// deselect old item, if any
|
||||
if (selectedItem !== null) {
|
||||
console.log("deselect");
|
||||
if (selectedItem.attr("data-old-fill")) {
|
||||
selectedItem.attr("fill", selectedItem.attr("data-old-fill"));
|
||||
selectedItem.attr("data-old-fill", null);
|
||||
}
|
||||
|
||||
if (selectedValue.name === d.name) {
|
||||
// remove selection on 2nd click - don't continue
|
||||
selectedItem = null;
|
||||
selectedValue = null;
|
||||
hideDetails()
|
||||
return;
|
||||
}
|
||||
|
||||
selectedItem = null;
|
||||
}
|
||||
|
||||
// select new item
|
||||
if (!self.attr("data-old-fill")) {
|
||||
self.attr("data-old-fill", self.attr("fill"));
|
||||
}
|
||||
|
||||
self.attr("fill", "goldenrod");
|
||||
selectedItem = self;
|
||||
selectedValue = d;
|
||||
|
||||
showDetails(d);
|
||||
},
|
||||
|
||||
onMouseover: function(d) {
|
||||
if (selectedItem != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = d3.select(this);
|
||||
if (!self.attr("data-old-fill")) {
|
||||
self.attr("data-old-fill", self.attr("fill"));
|
||||
}
|
||||
|
||||
d3.select(this).attr("fill", "darkturquoise");
|
||||
|
||||
showInfo(d);
|
||||
},
|
||||
|
||||
onMouseout: function(d) {
|
||||
if (selectedItem != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = d3.select(this);
|
||||
if (self.attr("data-old-fill")) {
|
||||
self.attr("fill", self.attr("data-old-fill"));
|
||||
self.attr("data-old-fill", null);
|
||||
}
|
||||
|
||||
hideInfo();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue