Graph in exercise statistics

This commit is contained in:
johnmgeiger
2016-03-22 09:23:21 +01:00
parent cc0a541add
commit b530ea47d9
7 changed files with 220 additions and 0 deletions

View File

@ -36,6 +36,7 @@ gem 'will_paginate', '~> 3.0'
gem 'tubesock' gem 'tubesock'
gem 'faye-websocket' gem 'faye-websocket'
gem 'nokogiri' gem 'nokogiri'
gem 'd3-rails'
group :development do group :development do
gem 'better_errors', platform: :ruby gem 'better_errors', platform: :ruby

View File

@ -98,6 +98,8 @@ GEM
concurrent-ruby (1.0.0-java) concurrent-ruby (1.0.0-java)
concurrent-ruby-ext (1.0.0) concurrent-ruby-ext (1.0.0)
concurrent-ruby (~> 1.0.0) concurrent-ruby (~> 1.0.0)
d3-rails (3.5.11)
railties (>= 3.1)
database_cleaner (1.5.1) database_cleaner (1.5.1)
debug_inspector (0.0.2) debug_inspector (0.0.2)
diff-lcs (1.2.5) diff-lcs (1.2.5)
@ -350,6 +352,7 @@ DEPENDENCIES
coffee-rails (~> 4.0.0) coffee-rails (~> 4.0.0)
concurrent-ruby (~> 1.0.0) concurrent-ruby (~> 1.0.0)
concurrent-ruby-ext (~> 1.0.0) concurrent-ruby-ext (~> 1.0.0)
d3-rails
database_cleaner database_cleaner
docker-api (~> 1.25.0) docker-api (~> 1.25.0)
factory_girl_rails (~> 4.0) factory_girl_rails (~> 4.0)

View File

@ -14,6 +14,7 @@
// //
//= require ace/ace //= require ace/ace
//= require chosen.jquery.min //= require chosen.jquery.min
//= require d3
//= require jquery.turbolinks //= require jquery.turbolinks
//= require jquery_ujs //= require jquery_ujs
//= require jstree/jstree.min //= require jstree/jstree.min

View File

@ -0,0 +1,177 @@
$(function() {
if ($.isController('exercises') && $('.graph-functions').isPresent()) {
var working_times = $('#data').data('working-time');
function get_minutes (time_stamp){
try{
hours = time_stamp.split(":")[0];
minutes = time_stamp.split(":")[1];
seconds = time_stamp.split(":")[2];
minutes = parseFloat(hours * 60) + parseInt(minutes);
return minutes
} catch (err){
return 0;
}
}
minutes_array = _.map(working_times,function(item){return get_minutes(item)});
maximum_minutes = _.max(minutes_array);
var minutes_count = new Array(maximum_minutes + 1);
for (var i = 0; i < maximum_minutes; i++){
for (var j = 0; j < minutes_array[i]; j++){
if (minutes_count[j] == undefined){
minutes_count[j] = 1;
} else{
minutes_count[j] += 1;
}
}
}
minutes_count[(maximum_minutes + 1)] = 0;
//$('.graph-functions').html("<p></p>")
//console.log(minutes_count) // THIS SHOWS THAT THE FINAL VALUES ARE 1 AND NOT ACTUALLY 0
// good to test at: localhost:3333/exercises/69/statistics
var width_ratio = .8;
var height_ratio = .7; // percent of height
// currently sets as percentage of window width, however, unfortunately
// is not yet responsive
function getWidth() {
if (self.innerHeight) {
return self.innerWidth;
}
if (document.documentElement && document.documentElement.clientWidth) {
return document.documentElement.clientWidth;
}
if (document.body) {
return document.body.clientWidth;
}
}
var margin = {top: 100, right: 20, bottom: 70, left: 70},//30,50
width = (getWidth() * width_ratio) - margin.left - margin.right,
height = (width * height_ratio) - margin.top - margin.bottom;
//var formatDate = d3.time.format("%M");
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height,0]); // - (height/20
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(20);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(20)
.innerTickSize(-width)
.outerTickSize(0);
var line = d3.svg.line()
.x(function(d,i) { return x(i); })
.y(function(d) { return y(d/minutes_count[0]*100); });
var svg = d3.select("#chart_1").append("svg") //PLACEMENT GOES HERE <---------------
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(d3.extent(minutes_count, function(d,i) { return (i); }));
y.domain(d3.extent(minutes_count, function(d) { return (d/minutes_count[0]*100); }));
svg.append("g") //x axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("text")// x axis label
.attr("class", "x axis")
.attr("text-anchor", "middle")
.attr("x", width/2)
.attr("y", height)
.attr("dy", ((height/20)+20) + 'px')
.text("Time Spent on Assignment (Minutes)")
.style('font-size',14);
svg.append("g") // y axis
.attr("class", "y axis")
.call(yAxis);
svg.append("text") // y axis label
.attr("transform", "rotate(-90)")
.attr("x", -height/2)
.attr("dy", "-3em")
.style("text-anchor", "middle")
.text("Students (%)")
.style('font-size',14);
svg.append("text")// Title
.attr("class", "x axis")
.attr("text-anchor", "middle")
.attr("x", (width/2))//+300)
.attr("y", 0)
.attr("dy", '-1.5em')
.text("Time Spent by Students on Exercise")
.style('font-size',20)
.style('text-decoration','underline');
svg.append("path")
.datum(minutes_count)
.attr("class", "line")
.attr('id','myPath')// new
.attr("stroke", "black")
.attr("stroke-width", 5)
.attr("fill", "none")// end new
.attr("d", line)//---
.on("mousemove", mMove)//new again
.append("title");
// THIS SHOULD DISPLAY THE X AND Y VALUES BUT
// THE RESULTS ARE WRONG AT THE END FOR SOME REASON
//function mMove() {
// var x_width = getWidth() * width_ratio;
// //var x_value = m[0]*(minutes_count.length/x_width);
//
// var y_height = x_width * height_ratio;
// //var y_value = (((y_height - m[1])/y_height)*100);
//
// //console.log('y is: ' + y_value);
// var m = d3.mouse(this);
// d3.select("#myPath").select("title")
// .text((y_height-m[1])/(y_height) * 100 + "% of Students" +"\n"+
// (m[0]*(minutes_count.length/x_width)) +" Minutes");//text(m[1]);
//}
//svg.append("rect") // border
// .attr("x", 0)
// .attr("y", 0)
// .attr("height", height)
// .attr("width", width)
// .style("stroke", "#229")
// .style("fill", "none")
// .style("stroke-width", 3);
}
});

View File

@ -11,3 +11,30 @@ input[type='file'] {
#exercise_template_code { #exercise_template_code {
font-family: monospace; font-family: monospace;
} }
// Graph Settings
.axis path {
fill: none;
stroke: #100;
shape-rendering: crispEdges;
}
.axis line {
fill: none;
stroke: #999;
//shape-rendering: crispEdges;
}
.y.axis path {
display: none;
}
.line {
fill: none;
stroke: orange;//steelblue;
stroke-width: 4px;
}
div#chart_1{
background-color: #FAFAFA;
}

View File

@ -49,3 +49,4 @@ div.negative-result {
-moz-box-shadow: 0px 0px 11px 1px rgba(222,0,0,1); -moz-box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
box-shadow: 0px 0px 11px 1px rgba(222,0,0,1); box-shadow: 0px 0px 11px 1px rgba(222,0,0,1);
} }

View File

@ -13,6 +13,16 @@ h1 = @exercise
- Hash[:internal_users => t('.internal_users'), :external_users => t('.external_users')].each_pair do |symbol, label| - Hash[:internal_users => t('.internal_users'), :external_users => t('.external_users')].each_pair do |symbol, label|
strong = label strong = label
-if symbol==:external_users
-working_time_array = []
- @exercise.send(symbol).distinct().each do |user|
-working_time = @exercise.average_working_time_for(user.id) or 0
-working_time_array.push working_time
hr
.hidden#data data-working-time=ActiveSupport::JSON.encode(working_time_array)
div#chart_1
.graph-functions
hr
.table-responsive .table-responsive
table.table.table-striped.sortable table.table.table-striped.sortable
thead thead