This is a bookmarklet, you can save it as a bookmark, and while Canvas is loaded, you click on the bookmark. After a minute, it'll show the statistics for your students across assignments.
Created
February 26, 2025 19:00
-
-
Save acbart/8feaae2d63b198e5d4f257a5a8f89eff to your computer and use it in GitHub Desktop.
Bookmarklet for visualizing grades in Canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
javascript:(function()%7B(async function() %7B%0A %0A function startDialog(title%2C body) %7B%0A if (%24('%23dialog').length %3D%3D 0) %7B%0A %24(document.body).append('<div title%3D"' %2B title %2B%0A '" id%3D"dialog"><%2Fdiv>')%3B%0A %7D%0A %24("%23dialog").dialog(%7B%0A autoOpen%3A false%2C%0A show%3A "blind"%2C%0A hide%3A "explode"%2C%0A width%3A '80%25'%2C%0A height%3A document.documentElement.clientHeight - 100%0A %7D)%3B%0A %24("%23dialog").dialog("open")%3B%0A %24('%23dialog').html(body)%3B%0A %7D%0A %0A function stats(arr) %7B%0A %2F%2F sort array ascending%0A const asc %3D arr.sort((a%2C b) %3D> a - b)%3B%0A const sum %3D arr %3D> arr.reduce((a%2C b) %3D> a %2B b%2C 0)%3B%0A const mean %3D sum(arr) %2F arr.length%3B%0A%0A %2F%2F sample standard deviation%0A const mu %3D mean%3B%0A const diffArr %3D arr.map(a %3D> (a - mu) ** 2)%3B%0A const std %3D Math.sqrt(sum(diffArr) %2F (arr.length - 1))%3B%0A%0A const quantile %3D (arr%2C q) %3D> %7B%0A const sorted %3D asc%3B%0A const pos %3D (sorted.length - 1) * q%3B%0A const base %3D Math.floor(pos)%3B%0A const rest %3D pos - base%3B%0A if (sorted%5Bbase %2B 1%5D !%3D%3D undefined) %7B%0A return sorted%5Bbase%5D %2B rest * (sorted%5Bbase %2B 1%5D - sorted%5Bbase%5D)%3B%0A %7D else %7B%0A return sorted%5Bbase%5D%3B%0A %7D%0A %7D%3B%0A%0A const q25 %3D quantile(arr%2C .25)%3B%0A%0A const q50 %3D quantile(arr%2C .50)%3B%0A%0A const q75 %3D quantile(arr%2C .75)%3B%0A %0A const min %3D asc%5B0%5D%3B%0A const max %3D asc%5Basc.length-1%5D%3B%0A%0A const median %3D q50%3B%0A return %5Bmean%2C std%2C min%2C q25%2C median%2C q75%2C max%5D%3B%0A %7D%0A%0A const submissions %3D (await %24.post("https%3A%2F%2Fudel.instructure.com%2Fapi%2Fgraphql"%2C %7B%0A "query"%3A %60query MyQuery(%24courseId%3A ID!) %7B%0A course(id%3A %24courseId) %7B%0A id%0A submissionsConnection %7B%0A nodes %7B%0A score%0A submissionStatus%0A state%0A assignment %7B%0A name%0A dueAt%0A assignmentGroup %7B%0A name%0A %7D%0A %7D%0A %7D%0A %7D%0A %7D%0A %7D%0A %0A %60%2C variables%3A %7BcourseId%3A ENV.COURSE.id%7D%0A %7D))%0A%0A let gathered %3D %7B%7D%3B%0A submissions.data.course.submissionsConnection.nodes.forEach(node %3D> %7B%0A if (!(node.assignment.name in gathered)) %7B%0A gathered%5Bnode.assignment.name%5D %3D %7Bdue%3A node.assignment.dueAt%2C missing%3A 0%2C submitted%3A %5B%5D%2C%0A group%3A node.assignment.assignmentGroup.name%7D%3B%0A %7D%0A const data %3D gathered%5Bnode.assignment.name%5D%3B%0A if (node.assignment.dueAt < data.due) %7B%0A data.due %3D node.assignment.dueAt%3B%0A %7D%0A if (node.state %3D%3D%3D "unsubmitted" %7C%7C node.score %3D%3D%3D 0) %7B data.missing %2B%3D 1 %7D else %7B%0A data.submitted.push(node.score)%3B%0A %7D%0A %7D)%3B%0A gathered %3D Object.entries(gathered).map((%5Bx%2Cy%5D)%3D>(%7Bname%3A x%2C ...y%7D))%3B%0A gathered.sort((a%2C b) %3D> ((a.group%2Ba.name).localeCompare((b.group%2Bb.name))))%3B%0A%0A const TABLE_HEADER %3D "<table class%3D'table table-bordered table-condensed table-striped'>"%3B%0A let table %3D %5B%60%24%7BTABLE_HEADER%7D%0A <tr>%0A <th>Group<%2Fth> %0A <th>Assignment<%2Fth>%0A <th>Missing<%2Fth>%0A <th>Submitted<%2Fth>%0A <th>Mean<%2Fth>%0A <th>Std<%2Fth>%0A <th>Min<%2Fth>%0A <th>25%25<%2Fth>%0A <th>Median<%2Fth>%0A <th>75%25<%2Fth>%0A <th>Max<%2Fth>%0A <%2Ftr>%60%5D%3B%0A gathered.forEach(g %3D> %7B%0A const gStats %3D stats(g.submitted).map(l%3D>Math.round(l*100)%2F100)%3B%0A table.push("<tr>"%2B%5Bg.group%2C g.name%2C g.missing%2C g.submitted.length%2C ...gStats%5D.map(s %3D> %60<td>%24%7Bs%7D<%2Ftd>%60).join("")%2B"<%2Ftr>")%3B%0A %7D)%3B%0A%0A let gradingStandards %3D await %24.get("https%3A%2F%2Fudel.instructure.com%2Fapi%2Fv1%2Fcourses%2F1697956%2Fgrading_standards")%3B%0A if (gradingStandards.length %3D%3D%3D 0) %7B%0A gradingStandards %3D %5B%7Bname%3A "A"%2C value%3A .94%7D%2C %7Bname%3A "A-"%2C value%3A .90%7D%2C %7Bname%3A "B%2B"%2C value%3A .87%7D%2C %7Bname%3A "B"%2C value%3A .84%7D%2C %7Bname%3A "B-"%2C value%3A .80%7D%2C %7Bname%3A "C%2B"%2C value%3A .77%7D%2C %7Bname%3A "C"%2C value%3A .74%7D%2C %7Bname%3A "C-"%2C value%3A .70%7D%2C %7Bname%3A "D%2B"%2C value%3A .67%7D%2C %7Bname%3A "D"%2C value%3A .64%7D%2C %7Bname%3A "D-"%2C value%3A .61%7D%2C %7Bname%3A "F"%2C value%3A 0%7D%5D%3B%0A %7D else %7B%0A gradingStandards %3D gradingStandards%5B0%5D%5B"grading_scheme"%5D%3B%0A %7D%0A const identifyGrade %3D (score) %3D> gradingStandards.find(p %3D> p.value <%3D score).name%3B%0A const grades %3D (await %24.post("https%3A%2F%2Fudel.instructure.com%2Fapi%2Fgraphql"%2C %7B%0A "query"%3A %60query MyQuery(%24courseId%3A ID!) %7B%0A course(id%3A %24courseId) %7B%0A enrollmentsConnection %7B%0A nodes %7B%0A grades %7B%0A finalScore%0A currentScore%0A unpostedFinalScore%0A unpostedCurrentScore%0A %7D%0A sisRole%0A %7D%0A %7D%0A %7D%0A %7D%0A %0A %60%2C variables%3A %7BcourseId%3A ENV.COURSE.id%7D%0A %7D))%0A gathered %3D %7B%7D%3B%0A %2F%2Flet distributions %3D Object.fromEntries(gradingStandards.map(g %3D> %5Bg.name%2C 0%5D))%3B%0A grades.data.course.enrollmentsConnection.nodes%0A .filter((n %3D> n.sisRole %3D%3D%3D 'student'))%0A .forEach(node %3D> %7B%0A Object.entries(node.grades).forEach((%5Bcat%2C score%5D) %3D> %7B%0A if (!(cat in gathered)) %7B%0A gathered%5Bcat%5D %3D %7Bname%3A cat%2C submitted%3A %5B%5D%2C%0A dists%3A Object.fromEntries(gradingStandards.map(g %3D> %5Bg.name%2C 0%5D))%7D%0A %7D%0A gathered%5Bcat%5D.submitted.push(score)%3B%0A gathered%5Bcat%5D.dists%5BidentifyGrade(score%2F100)%5D %2B%3D 1%3B%0A %7D)%3B%0A %7D)%3B%0A gathered %3D Object.entries(gathered).map((%5Bx%2Cy%5D)%3D>(%7Bname%3A x%2C ...y%7D))%3B%0A const afterTable %3D %5B"<table><tr>"%5D%3B%0A gathered.forEach(g %3D> %7B%0A afterTable.push("<td>")%3B%0A const gStats %3D stats(g.submitted).map(l%3D>Math.round(l*100)%2F100)%3B%0A table.push("<tr>"%2B%5B""%2C ""%2C g.name%2C g.submitted.length%2C ...gStats%5D.map(s %3D> %60<td>%24%7Bs%7D<%2Ftd>%60).join("")%2B"<%2Ftr>")%3B%0A afterTable.push(TABLE_HEADER)%3B%0A afterTable.push(%60<tr><th>%24%7Bg.name%7D<%2Fth><th><%2Fth><th><%2Fth><%2Ftr>%60)%3B%0A Object.entries(g.dists).forEach((%5Br%2C s%5D) %3D> %7B%0A const p %3D Math.round(100*s%2Fg.submitted.length)%3B%0A afterTable.push(%60<tr><td>%24%7Br%7D<%2Ftd><td>%24%7Bs%7D<%2Ftd><td>%24%7Bp%7D%25<%2Ftd><%2Ftr>%60)%3B%0A %7D)%3B%0A afterTable.push("<%2Ftable><%2Ftd>")%3B%0A %7D)%3B%0A afterTable.push("<%2Ftr><%2Ftable>")%3B%0A %0A table.push(%60<%2Ftable>%60)%3B%0A startDialog("Assignment Statistics"%2C table.join("") %2B afterTable.join(""))%3B%0A%0A%0A%2F%2F Missing%2C %23 Submitted%2C Mean Submitted Score%2C Std Submitted Score%2C Min%2C Lower Quartile%2C Median Submitted Score%2C Upper Quartile%2C Max%0A%7D)()%3B%7D)()%3B |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment