Permalink
Browse files

a huge build-out of grading trends along with rewriting the way Event…

… scores are pulled
  • Loading branch information...
1 parent 21b6962 commit a97e818620923aec0c125145c4ac94eb42f1a0a5 @agrobbin committed Dec 21, 2012
@@ -374,6 +374,25 @@ a.section {
}
}
+.graph {
+ float: left;
+ margin: 15px 0;
+ height: 225px;
+ width: 100%;
+ &.one-half {
+ width: 49%;
+ }
+ &.one-third {
+ width: 33%;
+ }
+ &.two-thirds {
+ width: 65%;
+ }
+ &.sep {
+ width: 2%;
+ }
+}
+
.loading {
@include gradient($end: #ccc);
@include css3-attribute(border-radius, 5px);
@@ -80,20 +80,6 @@ table#assignment-list {
padding-right: 7px;
}
}
- .graph {
- float: left;
- margin-top: 15px;
- height: 225px;
- &.one-third {
- width: 33%;
- }
- &.two-thirds {
- width: 65%;
- }
- &.third-sep {
- width: 2%;
- }
- }
}
}
}
@@ -22,7 +22,7 @@
padding-left: 11px;
}
}
- span#section-settings {
+ span.sub-menu {
> div {
position: absolute;
width: 170px;
@@ -2,7 +2,7 @@ class GradesController < InstitutionsController
before_filter :require_professor, except: :index
before_filter :get_course_and_section, if: Proc.new {|p| p.params[:section_id]}
- before_filter :get_event, except: :index
+ before_filter :get_event, only: [:edit, :update, :analysis]
before_filter :get_grade_fields, only: [:edit, :update]
ajax :analysis, :edit
@@ -14,26 +14,20 @@ def index
@student_grades = Grade.student_scores_for(current_user, @assignments) if current_user.is_a?(Student)
end
+ def trends
+ @title = "Grading Trends for #{@section.title}"
+
+ events = @section.events.assignments.all
+
+ @chart_1 = Chart.student_grades(@section, events)
+ @chart_2 = Chart.assignments_by_student(events)
+ @chart_3 = Chart.letter_grades(events, renderTo: 'graph_all_assignments', title: 'All Assignments By Letter Grade')
+ @chart_4 = Chart.grade_range(events, renderTo: 'graph_by_assignment', title: 'All Grades By Assignment', with_legend: true)
+ end
+
def analysis
- grades = Grade.letter_scores(@event).values
-
- @chart_1 = Highcharts.new
- @chart_1.chart(renderTo: "assignment_#{@event.id}_analysis_chart_1", type: 'scatter')
- @chart_1.title(text: nil)
- @chart_1.legend(enabled: false)
- @chart_1.xAxis([{title: 'Grade', categories: ['A', 'B', 'C', 'D', 'F']}])
- @chart_1.yAxis([{title: '# of Students', min: 0, allowDecimals: false}])
- @chart_1.series([{name: 'Grades', type: 'column', data: grades}])
-
- grades = @event.grades.select('score, COUNT(*) AS count').order(:score).group(:score)
- grades = grades.collect(&:attributes).collect(&:values)
-
- @chart_2 = Highcharts.new
- @chart_2.chart("assignment_#{@event.id}_analysis_chart_2")
- @chart_2.tooltip(formatter: "function(){ return '<b>' + this.x + ' / #{@event.points_possible}</b><br/>' + this.y + ' Student' + (this.y == 1 ? '' : 's'); }")
- @chart_2.xAxis(title: 'Grade', tickInterval: 10, min: 0, max: @event.points_possible, categories: (0..@event.points_possible).to_a)
- @chart_2.yAxis('# of Students')
- @chart_2.series(name: 'Grades', data: grades)
+ @chart_1 = Chart.letter_grades(@event, renderTo: "assignment_#{@event.id}_analysis_chart_1")
+ @chart_2 = Chart.grade_range(@event, renderTo: "assignment_#{@event.id}_analysis_chart_2")
end
def update
View
@@ -1,5 +1,9 @@
class Grade < ActiveRecord::Base
+ scope :for_events, lambda {|events| where(event_id: Array.wrap(events).collect(&:id))}
+ scope :scores_for, lambda {|events| for_events(events).select('event_id, AVG(score) AS average, MIN(score) AS min, MAX(score) AS max').group(:event_id)}
+ scope :student_scores_for, lambda {|student, events| for_events(events).select('event_id, score').where(student_id: student.id)}
+
belongs_to :event
belongs_to :student
@@ -8,60 +12,5 @@ class Grade < ActiveRecord::Base
attr_accessible :student_id, :score
- # This pulls out the score calculation logic for Professors in order to avoid a potentially horrendous N+1 problem.
- # Pass an array of Events and expect the following response:
- #
- # {1: {average: "75 / 100", range: "50-100 / 100"}, 2: {average: "75 / 100", range: "50-100 / 100"}}
- #
- # Where the `key` is the ID of the Event.
- def self.scores_for(*events)
- events.flatten!
- grades = Grade.select("event_id, AVG(score) AS average, MIN(score) AS min, MAX(score) AS max").where(event_id: events.collect(&:id)).group(:event_id)
- grades.inject({}) do |scores, grade|
- event = events.find {|e| grade.event_id == e.id}
- scores[event.id] = {
- average: "#{grade.average.to_f} / #{event.points_possible}",
- range: "#{grade.min}-#{grade.max} / #{event.points_possible}"
- }
- scores
- end
- end
-
- # Pull out all of the scores for a particular Student and a particular set of Events.
- # Pass a student and then an array of Events and expect the following response:
- #
- # {1: 95, 2: 100, 3: 77, 4: 82, 5: 86}
- #
- # Where the `key is the ID of the Event.
- def self.student_scores_for(student, *events)
- events.flatten!
- grades = Grade.select("event_id, score").where(event_id: events.collect(&:id), student_id: student.id)
- grades.inject({}) do |scores, grade|
- event = events.find {|e| grade.event_id == e.id}
- scores[event.id] = "#{grade.score} / #{event.points_possible}"
- scores
- end
- end
-
- # This take the grades for an Event and maps them to the appropriate letter grade. The final result is a Hash
- # of letter: count key/value pairs:
- #
- # {'A' => 1, 'B' => 2, 'C' => 3, 'D' => 2, 'F' => 1}
- def self.letter_scores(event)
- letter_map = {
- 'A' => 90,
- 'B' => 80,
- 'C' => 70,
- 'D' => 65,
- 'F' => 0
- }
-
- grades = event.grades.pluck(:score)
- grades = grades.group_by {|g| letter_map.find {|l, min| g >= min }.first }
- letter_map.inject({}) do |scores, (letter, min)|
- scores[letter] = grades[letter].try(:length) || 0
- scores
- end
- end
end
@@ -1,5 +1,5 @@
-- grade = (@grades || Grade.scores_for(assignment))[assignment.id]
-- student_grade = @student_grades[assignment.id] if current_user.is_a?(Student)
+- grade = (@grades || Grade.scores_for(assignment)).find {|g| g.event_id == assignment.id}
+- student_grade = student_grade = @student_grades.find {|g| g.event_id == assignment.id} if current_user.is_a?(Student)
%tr.assignment{id: "assignment_#{assignment.id}", class: current_user.is_a?(Professor) && !assignment.visible ? 'invisible' : nil, data: {sort: assignment.ends_at}}
%td
- if current_user.is_a?(Professor)
@@ -12,12 +12,12 @@
= link_to assignment.title, section_event_path(@section, assignment), class: 'modal'
%td= assignment.ends_at.strftime("%b. %d, %Y by %I:%M%P")
- if current_user.is_a?(Student)
- %td= student_grade || 'N/A'
+ %td= student_grade ? "#{student_grade.score} / #{assignment.points_possible}" : 'N/A'
- if @section.grading_system == 'weight'
%td= "#{assignment.weight}%"
- if grade
- %td= grade[:average]
- %td= grade[:range]
+ %td= "#{number_with_precision grade.average, precision: 2, strip_insignificant_zeros: true} / #{assignment.points_possible}"
+ %td= "#{grade.min}-#{grade.max} / #{assignment.points_possible}"
- else
%td N/A
%td N/A
@@ -4,7 +4,7 @@
%h3.icon-bar-chart Assignment Grade Analysis
= @chart_1
.graph.one-third{id: "assignment_#{@event.id}_analysis_chart_1"}
- .graph.third-sep
+ .graph.sep
= @chart_2
.graph.two-thirds{id: "assignment_#{@event.id}_analysis_chart_2"}
.clear
@@ -0,0 +1,17 @@
+= render 'sections/subnavigation'
+
+#course
+ = render 'sections/heading'
+ %br
+ %h2 Grading Trends
+ %p Seeing your assignments broken down into readable graphs and charts sometimes makes it easier to see trends, both good and bad. Use these to easily notice when a particular student might be having difficulties. Certain tests may also be considerably more difficult, which could be difficult to see without comparisons to other tests and assignments given.
+ = @chart_1
+ .graph.one-third#graph_student_grades
+ .graph.sep
+ = @chart_2
+ .graph.two-thirds#graph_all_assignments_by_student
+ = @chart_3
+ .graph.one-third#graph_all_assignments
+ .graph.sep
+ = @chart_4
+ .graph.two-thirds#graph_by_assignment
@@ -5,7 +5,7 @@
#subnavigation.sortable
= yield :subnavigation
- if current_user.is_a?(Professor)
- %span#section-settings
+ %span.sub-menu
= link_to 'Settings', edit_section_path(@section), class: 'icon-cogs modal button'
%div
= link_to 'Create a Notice', new_section_notice_path(@section), class: 'icon-envelope-alt modal'
@@ -14,7 +14,11 @@
= link_to 'Create an Event', new_section_event_path(@section), class: 'icon-calendar modal'
= link_to 'Create a Page', new_section_page_path(@section), class: 'icon-bookmark modal'
= link_to 'Homepage', section_path(@section), class: "icon-home button #{params[:controller] == 'sections' && params[:action] != 'roster' ? 'active' : nil}"
- = link_to 'Gradebook', section_grades_path(@section), class: "icon-check button #{params[:controller] == 'grades' ? 'active' : nil}"
+ %span.sub-menu
+ = link_to 'Gradebook', section_grades_path(@section), class: "icon-check button #{params[:controller] == 'grades' ? 'active' : nil}"
+ - if current_user.is_a?(Professor)
+ %div
+ = link_to 'Grading Trends', trends_section_grades_path(@section), class: 'icon-bar-chart'
= link_to 'Documents', section_documents_path(@section), class: "icon-file button #{params[:controller] == 'documents' ? 'active' : nil}"
= link_to 'Events', section_events_path(@section), class: "icon-calendar button #{params[:controller] == 'events' ? 'active' : nil}"
= link_to 'Roster', roster_section_path(@section), class: "icon-group button #{params[:controller] == 'sections' && params[:action] == 'roster' ? 'active' : nil}"
@@ -1,4 +1,5 @@
require 'calendar'
+require 'chart'
require 'notifiable'
require 'positionable'
require 'routing'
View
@@ -38,7 +38,9 @@
end
end
get 'roster', on: :member
- get 'grades', to: 'grades#index'
+ resources :grades, only: [:index] do
+ get 'trends', on: :collection
+ end
end
get '/events(/:year(/:month))', to: 'events#index', as: 'events', constraints: {year: /20[0-9]{2}/, month: /(?:0?[1-9]|1[0-2])/}
get '/grades', to: 'grades#index', as: 'grades'
Oops, something went wrong.

0 comments on commit a97e818

Please sign in to comment.