Visualize exercise working times in exercise collection statstics
This commit is contained in:
103
app/assets/javascripts/exercise_collections.js.erb
Normal file
103
app/assets/javascripts/exercise_collections.js.erb
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
$(function() {
|
||||||
|
if ($.isController('exercise_collections')) {
|
||||||
|
var data = $('#data').data('working-times');
|
||||||
|
var averageWorkingTimeValue = parseFloat($('#data').data('average-working-time'));
|
||||||
|
|
||||||
|
var margin = { top: 30, right: 40, bottom: 30, left: 50 },
|
||||||
|
width = 720 - margin.left - margin.right,
|
||||||
|
height = 500 - margin.top - margin.bottom;
|
||||||
|
|
||||||
|
var x = d3.scaleBand().range([0, width]);
|
||||||
|
var y = d3.scaleLinear().range([height, 0]);
|
||||||
|
|
||||||
|
var xAxis = d3.axisBottom(x);
|
||||||
|
var yAxisLeft = d3.axisLeft(y);
|
||||||
|
|
||||||
|
var tooltip = d3.select("#graph").append("div").attr("class", "exercise-id-tooltip");
|
||||||
|
|
||||||
|
var averageWorkingTime = d3.line()
|
||||||
|
.x(function (d) { return x(d.index) + x.bandwidth()/2; })
|
||||||
|
.y(function () { return y(averageWorkingTimeValue); });
|
||||||
|
|
||||||
|
var minWorkingTime = d3.line()
|
||||||
|
.x(function (d) { return x(d.index) + x.bandwidth()/2; })
|
||||||
|
.y(function () { return y(0.1*averageWorkingTimeValue); });
|
||||||
|
|
||||||
|
var maxWorkingTime = d3.line()
|
||||||
|
.x(function (d) { return x(d.index) + x.bandwidth()/2; })
|
||||||
|
.y(function () { return y(2*averageWorkingTimeValue); });
|
||||||
|
|
||||||
|
var svg = d3.select('#graph')
|
||||||
|
.append("svg")
|
||||||
|
.attr("width", width + margin.left + margin.right)
|
||||||
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
|
.append("g")
|
||||||
|
.attr("transform",
|
||||||
|
"translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
|
// Get the data
|
||||||
|
data = Object.keys(data).map(function (key, index) {
|
||||||
|
return {
|
||||||
|
index: index,
|
||||||
|
exercise_id: parseInt(key),
|
||||||
|
working_time: parseFloat(data[key])
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scale the range of the data
|
||||||
|
x.domain(data.map(function (d) { return d.index; }));
|
||||||
|
y.domain([0, d3.max(data, function (d) { return d.working_time; })]);
|
||||||
|
|
||||||
|
// Add the X Axis
|
||||||
|
svg.append("g")
|
||||||
|
.attr("class", "x axis")
|
||||||
|
.attr("transform", "translate(0," + height + ")")
|
||||||
|
.call(xAxis);
|
||||||
|
|
||||||
|
// Add the Y Axis
|
||||||
|
svg.append("g")
|
||||||
|
.attr("class", "y axis")
|
||||||
|
.style("fill", "steelblue")
|
||||||
|
.call(yAxisLeft);
|
||||||
|
|
||||||
|
// Draw the bars
|
||||||
|
svg.selectAll("bar")
|
||||||
|
.data(data)
|
||||||
|
.enter()
|
||||||
|
.append("rect")
|
||||||
|
.style("fill", "#008cba")
|
||||||
|
.style("cursor", "pointer")
|
||||||
|
.on("mousemove", function (d){
|
||||||
|
tooltip
|
||||||
|
.style("left", d3.event.pageX - 50 + "px")
|
||||||
|
.style("top", d3.event.pageY + 50 + "px")
|
||||||
|
.style("display", "inline-block")
|
||||||
|
.html("<%= I18n.t('activerecord.models.exercise.one') %> ID: " + d.exercise_id + "<br>" +
|
||||||
|
"<%= I18n.t('exercises.statistics.average_worktime') %>: " + d.working_time + "s");
|
||||||
|
})
|
||||||
|
.on("mouseout", function (){ tooltip.style("display", "none");})
|
||||||
|
.on("click", function (d) {
|
||||||
|
window.location.href = "/exercises/" + d.exercise_id + "/statistics";
|
||||||
|
})
|
||||||
|
.attr("x", function (d) { return x(d.index); })
|
||||||
|
.attr("width", x.bandwidth())
|
||||||
|
.attr("y", function (d) { return y(d.working_time); })
|
||||||
|
.attr("height", function (d) { return height - y(d.working_time); });
|
||||||
|
|
||||||
|
// Add the average working time path
|
||||||
|
svg.append("path")
|
||||||
|
.datum(data)
|
||||||
|
.attr("class", "line average-working-time")
|
||||||
|
.attr("d", averageWorkingTime);
|
||||||
|
|
||||||
|
// Add the anomaly paths (min/max average exercise working time)
|
||||||
|
svg.append("path")
|
||||||
|
.datum(data)
|
||||||
|
.attr("class", "line minimum-working-time")
|
||||||
|
.attr("d", minWorkingTime);
|
||||||
|
svg.append("path")
|
||||||
|
.datum(data)
|
||||||
|
.attr("class", "line maximum-working-time")
|
||||||
|
.attr("d", maxWorkingTime);
|
||||||
|
}
|
||||||
|
});
|
22
app/assets/stylesheets/exercise_collections.scss
Normal file
22
app/assets/stylesheets/exercise_collections.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
path.line.minimum-working-time {
|
||||||
|
stroke: #8efa00;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.line.average-working-time {
|
||||||
|
stroke: #ffca00;
|
||||||
|
}
|
||||||
|
|
||||||
|
path.line.maximum-working-time {
|
||||||
|
stroke: #ff2600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-id-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
min-width: 80px;
|
||||||
|
height: auto;
|
||||||
|
background: none repeat scroll 0 0 #ffffff;
|
||||||
|
border: 1px solid #008cba;
|
||||||
|
padding: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -4,12 +4,16 @@ class ExerciseCollection < ActiveRecord::Base
|
|||||||
has_and_belongs_to_many :exercises
|
has_and_belongs_to_many :exercises
|
||||||
belongs_to :user, polymorphic: true
|
belongs_to :user, polymorphic: true
|
||||||
|
|
||||||
def average_working_time
|
def exercise_working_times
|
||||||
working_times = {}
|
working_times = {}
|
||||||
exercises.each do |exercise|
|
exercises.each do |exercise|
|
||||||
working_times[exercise.id] = time_to_f exercise.average_working_time
|
working_times[exercise.id] = time_to_f exercise.average_working_time
|
||||||
end
|
end
|
||||||
working_times.values.reduce(:+) / working_times.size
|
working_times
|
||||||
|
end
|
||||||
|
|
||||||
|
def average_working_time
|
||||||
|
exercise_working_times.values.reduce(:+) / exercises.size
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_s
|
def to_s
|
||||||
|
@ -4,3 +4,6 @@ h1 = @exercise_collection
|
|||||||
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
|
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
|
||||||
= row(label: 'exercise_collections.exercises', value: @exercise_collection.exercises.count)
|
= row(label: 'exercise_collections.exercises', value: @exercise_collection.exercises.count)
|
||||||
= row(label: 'exercises.statistics.average_worktime', value: @exercise_collection.average_working_time.round(3).to_s + 's')
|
= row(label: 'exercises.statistics.average_worktime', value: @exercise_collection.average_working_time.round(3).to_s + 's')
|
||||||
|
|
||||||
|
#graph
|
||||||
|
#data.hidden(data-working-times=ActiveSupport::JSON.encode(@exercise_collection.exercise_working_times) data-average-working-time=@exercise_collection.average_working_time)
|
||||||
|
Reference in New Issue
Block a user