Skip to content

Commit

Permalink
#3432 Add graph to show course grade distribution (sakaiproject#4213)
Browse files Browse the repository at this point in the history
* Remove unused code and cleanup other compiler warnings

* #3432 made the hart component common and wired up the grading schema page to add it

* #3432 Add first iteration of the bar chart for course grade stats

* #3432 invert x-axis of chart. Add info about students with overrides that may affect the distribution
Move comparators out of business service into own classes.

* #3432 Add stats underneath the graph
Add note about clicking save changes
  • Loading branch information
steveswinsburg authored and payten committed Apr 11, 2017
1 parent d4813e6 commit 7c3f39d
Show file tree
Hide file tree
Showing 15 changed files with 613 additions and 285 deletions.
4 changes: 4 additions & 0 deletions gradebookng/tool/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@
<artifactId>jfreechart</artifactId>
<version>1.0.19</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
</dependency>
</dependencies>
<build>
<resources>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ settingspage.gradeentry.heading = Grade Entry
settingspage.graderelease.heading = Grade Release Rules
settingspage.categories.heading = Categories & Weighting
settingspage.gradingschema.heading = Grading Schema
settingspage.gradingschema.chart.heading = Course Grade Distribution
settingspage.gradingschema.chart.xaxis = Course Grade
settingspage.gradingschema.overrides = Course grade overrides exist for the following students which may affect the chart distribution:

settingspage.gradeentry.instructions = How will graders enter grades into this gradebook?
settingspage.gradeentry.points = Points
Expand Down Expand Up @@ -388,6 +391,7 @@ settingspage.categories.runningtotalerror=Weighting for the categories must equa
settingspage.gradingschema.type = Grade Type
settingspage.gradingschema.grade = Grade
settingspage.gradingschema.minpercent = Minimum %
settingspage.gradingschema.refresh = If you make changes to the Grade Type or Minimum %, the chart will be updated after you click 'Save Changes'.

settingspage.update.success = The settings were successfully updated
settingspage.update.failure.categorymissingweight = No weight specified for a category, but weightings enabled
Expand Down Expand Up @@ -438,7 +442,14 @@ label.statistics.deviation = Standard Deviation
label.statistics.chart.extracredit = EC
label.statistics.chart.xaxis = Percentage Scored
label.statistics.chart.yaxis = Number of Students
label.statistics.chart.tooltip = {2} score(s) in {1}% range
label.statistics.chart.tooltip = {2} score(s) in {1} range

label.statistics.coursegrade.average = Average grade
label.statistics.coursegrade.graded = Total graded students
label.statistics.coursegrade.median = Median grade
label.statistics.coursegrade.lowest = Lowest grade
label.statistics.coursegrade.highest = Highest grade
label.statistics.coursegrade.deviation = Standard deviation

coursegrade.option.override = Course Grade Override
coursegrade.option.overridelog = Course Grade Override Log
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.sakaiproject.gradebookng.business;

import java.util.Comparator;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.math.NumberUtils;
import org.sakaiproject.gradebookng.business.model.GbGradeInfo;
import org.sakaiproject.gradebookng.business.model.GbStudentGradeInfo;

/**
* Comparator class for sorting an assignment by the grades.
*
* Note that this must have the assignmentId set into it so we can extract the appropriate grade entry from the map that each student
* has.
*
*/
public class AssignmentGradeComparator implements Comparator<GbStudentGradeInfo> {

private final long assignmentId;

public AssignmentGradeComparator(final long assignmentId) {
this.assignmentId = assignmentId;
}

@Override
public int compare(final GbStudentGradeInfo g1, final GbStudentGradeInfo g2) {

final GbGradeInfo info1 = g1.getGrades().get(this.assignmentId);
final GbGradeInfo info2 = g2.getGrades().get(this.assignmentId);

// for proper number ordering, these have to be numerical
final Double grade1 = (info1 != null) ? NumberUtils.toDouble(info1.getGrade()) : null;
final Double grade2 = (info2 != null) ? NumberUtils.toDouble(info2.getGrade()) : null;

return new CompareToBuilder().append(grade1, grade2).toComparison();

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.sakaiproject.gradebookng.business;

import java.util.Comparator;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.sakaiproject.gradebookng.business.model.GbStudentGradeInfo;

/**
* Comparator class for sorting a category by the subtotals
*
* Note that this must have the categoryId set into it so we can extract the appropriate grade entry from the map that each student has.
*
*/
public class CategorySubtotalComparator implements Comparator<GbStudentGradeInfo> {

private final long categoryId;

public CategorySubtotalComparator(final long categoryId) {
this.categoryId = categoryId;
}

@Override
public int compare(final GbStudentGradeInfo g1, final GbStudentGradeInfo g2) {

final Double subtotal1 = g1.getCategoryAverages().get(this.categoryId);
final Double subtotal2 = g2.getCategoryAverages().get(this.categoryId);

return new CompareToBuilder().append(subtotal1, subtotal2).toComparison();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.sakaiproject.gradebookng.business;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.sakaiproject.gradebookng.business.model.GbStudentGradeInfo;
import org.sakaiproject.service.gradebook.shared.CourseGrade;
import org.sakaiproject.service.gradebook.shared.GradebookInformation;

/**
* Comparator class for sorting by course grade, first by the letter grade's index in the gradebook's grading scale and then by the
* number of points the student has earned.
*/
public class CourseGradeComparator implements Comparator<GbStudentGradeInfo> {

private List<String> ascendingGrades;

public CourseGradeComparator(final GradebookInformation gradebookInformation) {
final Map<String, Double> gradeMap = gradebookInformation.getSelectedGradingScaleBottomPercents();
this.ascendingGrades = new ArrayList<>(gradeMap.keySet());
this.ascendingGrades.sort(new Comparator<String>() {
@Override
public int compare(final String a, final String b) {
return new CompareToBuilder()
.append(gradeMap.get(a), gradeMap.get(b))
.toComparison();
}
});
}

@Override
public int compare(final GbStudentGradeInfo g1, final GbStudentGradeInfo g2) {
final CourseGrade cg1 = g1.getCourseGrade().getCourseGrade();
final CourseGrade cg2 = g2.getCourseGrade().getCourseGrade();

String letterGrade1 = cg1.getMappedGrade();
if (cg1.getEnteredGrade() != null) {
letterGrade1 = cg1.getEnteredGrade();
}
String letterGrade2 = cg2.getMappedGrade();
if (cg2.getEnteredGrade() != null) {
letterGrade2 = cg2.getEnteredGrade();
}

final int gradeIndex1 = this.ascendingGrades.indexOf(letterGrade1);
final int gradeIndex2 = this.ascendingGrades.indexOf(letterGrade2);

final Double calculatedGrade1 = cg1.getCalculatedGrade() == null ? null : Double.valueOf(cg1.getCalculatedGrade());
final Double calculatedGrade2 = cg2.getCalculatedGrade() == null ? null : Double.valueOf(cg2.getCalculatedGrade());

return new CompareToBuilder()
.append(gradeIndex1, gradeIndex2)
.append(calculatedGrade1, calculatedGrade2)
.toComparison();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.sakaiproject.gradebookng.business;

import java.text.Collator;
import java.util.Comparator;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.sakaiproject.user.api.User;

/**
* Comparator class for sorting a list of users by first name.
* Secondary sort is on last name to maintain consistent order for those with the same first name
*/
public class FirstNameComparator implements Comparator<User> {

private final Collator collator = Collator.getInstance();

@Override
public int compare(final User u1, final User u2) {
this.collator.setStrength(Collator.PRIMARY);
return new CompareToBuilder()
.append(u1.getFirstName(), u2.getFirstName(), this.collator)
.append(u1.getLastName(), u2.getLastName(), this.collator)
.toComparison();
}


}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.sakaiproject.gradebookng.business;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -18,10 +17,8 @@
import java.util.stream.Collectors;

import javax.xml.bind.JAXBException;
import lombok.RequiredArgsConstructor;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.math.NumberUtils;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.SecurityService;
Expand Down Expand Up @@ -124,9 +121,6 @@ public class GradebookNgBusinessService {
public static final String ASSIGNMENT_ORDER_PROP = "gbng_assignment_order";
public static final String ICON_SAKAI = "icon-sakai--";


private final Collator collator = Collator.getInstance();

/**
* Get a list of all users in the current site that can have grades
*
Expand Down Expand Up @@ -425,7 +419,7 @@ public Map<String, CourseGrade> getCourseGrades(final List<String> studentUuids)

final Gradebook gradebook = this.getGradebook();
if (gradebook != null) {
rval = this.gradebookService.getCourseGradeForStudents(gradebook.getUid(), this.getGradeableUsers());
rval = this.gradebookService.getCourseGradeForStudents(gradebook.getUid(), studentUuids);
}
return rval;
}
Expand Down Expand Up @@ -1255,32 +1249,6 @@ private void updateAssignmentCategorizedOrder(final String gradebookId, final Lo
new Integer(order));
}

/**
* Comparator class for sorting a list of users by last name Secondary sort is on first name to maintain consistent order for those with
* the same last name
*/
class LastNameComparator implements Comparator<User> {
@Override
public int compare(final User u1, final User u2) {
GradebookNgBusinessService.this.collator.setStrength(Collator.PRIMARY);
return new CompareToBuilder().append(u1.getLastName(), u2.getLastName(), GradebookNgBusinessService.this.collator)
.append(u1.getFirstName(), u2.getFirstName(), GradebookNgBusinessService.this.collator).toComparison();
}
}

/**
* Comparator class for sorting a list of users by first name Secondary sort is on last name to maintain consistent order for those with
* the same first name
*/
class FirstNameComparator implements Comparator<User> {
@Override
public int compare(final User u1, final User u2) {
GradebookNgBusinessService.this.collator.setStrength(Collator.PRIMARY);
return new CompareToBuilder().append(u1.getFirstName(), u2.getFirstName(), GradebookNgBusinessService.this.collator)
.append(u1.getLastName(), u2.getLastName(), GradebookNgBusinessService.this.collator).toComparison();
}
}

/**
* Get a list of edit events for this gradebook. Excludes any events for the current user
*
Expand Down Expand Up @@ -1991,103 +1959,4 @@ public String getIconClass(final Assignment assignment) {
return iconClass;
}


/**
* Comparator class for sorting an assignment by the grades.
*
* Note that this must have the assignmentId set into it so we can extract the appropriate grade entry from the map that each student
* has.
*
*/
@RequiredArgsConstructor
class AssignmentGradeComparator implements Comparator<GbStudentGradeInfo> {

private final long assignmentId;

@Override
public int compare(final GbStudentGradeInfo g1, final GbStudentGradeInfo g2) {

final GbGradeInfo info1 = g1.getGrades().get(this.assignmentId);
final GbGradeInfo info2 = g2.getGrades().get(this.assignmentId);

// for proper number ordering, these have to be numerical
final Double grade1 = (info1 != null) ? NumberUtils.toDouble(info1.getGrade()) : null;
final Double grade2 = (info2 != null) ? NumberUtils.toDouble(info2.getGrade()) : null;

return new CompareToBuilder().append(grade1, grade2).toComparison();

}
}

/**
* Comparator class for sorting a category by the subtotals
*
* Note that this must have the categoryId set into it so we can extract the appropriate grade entry from the map that each student has.
*
*/
@RequiredArgsConstructor
class CategorySubtotalComparator implements Comparator<GbStudentGradeInfo> {

private final long categoryId;

@Override
public int compare(final GbStudentGradeInfo g1, final GbStudentGradeInfo g2) {

final Double subtotal1 = g1.getCategoryAverages().get(this.categoryId);
final Double subtotal2 = g2.getCategoryAverages().get(this.categoryId);

return new CompareToBuilder().append(subtotal1, subtotal2).toComparison();

}
}

/**
* Comparator class for sorting by course grade, first by the letter grade's index in the gradebook's grading scale and then by the
* number of points the student has earned.
*
*/
class CourseGradeComparator implements Comparator<GbStudentGradeInfo> {

private List<String> ascendingGrades;

public CourseGradeComparator(final GradebookInformation gradebookInformation) {
final Map<String, Double> gradeMap = gradebookInformation.getSelectedGradingScaleBottomPercents();
this.ascendingGrades = new ArrayList<>(gradeMap.keySet());
this.ascendingGrades.sort(new Comparator<String>() {
@Override
public int compare(final String a, final String b) {
return new CompareToBuilder()
.append(gradeMap.get(a), gradeMap.get(b))
.toComparison();
}
});
}

@Override
public int compare(final GbStudentGradeInfo g1, final GbStudentGradeInfo g2) {
final CourseGrade cg1 = g1.getCourseGrade().getCourseGrade();
final CourseGrade cg2 = g2.getCourseGrade().getCourseGrade();

String letterGrade1 = cg1.getMappedGrade();
if (cg1.getEnteredGrade() != null) {
letterGrade1 = cg1.getEnteredGrade();
}
String letterGrade2 = cg2.getMappedGrade();
if (cg2.getEnteredGrade() != null) {
letterGrade2 = cg2.getEnteredGrade();
}

final int gradeIndex1 = this.ascendingGrades.indexOf(letterGrade1);
final int gradeIndex2 = this.ascendingGrades.indexOf(letterGrade2);

final Double calculatedGrade1 = cg1.getCalculatedGrade() == null ? null : Double.valueOf(cg1.getCalculatedGrade());
final Double calculatedGrade2 = cg2.getCalculatedGrade() == null ? null : Double.valueOf(cg2.getCalculatedGrade());

return new CompareToBuilder()
.append(gradeIndex1, gradeIndex2)
.append(calculatedGrade1, calculatedGrade2)
.toComparison();
}
}

}
Loading

0 comments on commit 7c3f39d

Please sign in to comment.