Public
Edited
Jan 7, 2024
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = Plot.plot({
width: width,
height: height,
x: {domain: [0, 110], label: null},
y: {ticks: [], label: "Student"},
marginRight: 50,
legend: {marginBottom: 50},
color: {domain: allAssignmnets, range: colorRange, legend: true, width: 500, columns:1},
marks: [
Plot.text(gradeBoundaries, {x: "x", text: "grade", textAnchor: "start", dx: 5, dy: -280}),
Plot.text(gradeBoundaries, {x: "x", text: "grade", textAnchor: "start", dx: 5, dy: 270}),
Plot.ruleX([0, 30, 40, 50, 60, 63, 67, 70, 73, 77, 80, 83, 87, 90, 93, 100]),
Plot.barX(tidydata.filter(f => f.points && f.assignment !== "total"), {
x: "points",
y: "student",
fill: "assignment",
stroke: "white",
sort: {y: "-x"},
tip: true
}),
showForecast ?
Plot.link(tidydata.filter(f => f.assignment === "total"), {
x1: d => d["points"] * forecastFactor + groupProjectMin,
x2: d => d["points"] * forecastFactor + groupProjectMax,
y1: d => d["student"],
y2: d => d["student"],
markerEnd: "dot",
markerStart: "dot",
strokeWidth: 2,
stroke: "black"
}) : null,
showForecast ?
Plot.text(tidydata.filter(f => f.assignment === "total"), {
x: d => d["points"] * forecastFactor + groupProjectMin,
y: "student",
text: d => d["points"] === d3.max(tidydata, a => a.points) ? "FORECAST low & high": "",
textAnchor: "start",
dx: 5,
dy: -10,
stroke: "white",
strokeWidth: 4,
paintOrder: "stroke",
fill: "black",
fontSize: "1em"
}) : null,
Plot.text(tidydata.filter(f => f.assignment === "total"), {
x: "points",
y: "student",
text: (d) => Math.floor(d.points),
textAnchor: "start",
fill: "black",
stroke: "white",
strokeWidth: 3,
paintOrder: "stroke",
dx: 2
}),
Plot.image(tidydata.slice(0,1), {
x: width,
y: "student",
width: 200,
src: "https://i.redd.it/1o8zqjm9bqc81.png"
})
]
})
Insert cell
showForecast = percentCourseComplete <= 100
Insert cell
gradeBoundaries = [
{x: 0, grade: "F"},
{x: 60, grade: "D-"},
{x: 63, grade: "D"},
{x: 67, grade: "D+"},
{x: 70, grade: "C-"},
{x: 73, grade: "C"},
{x: 77, grade: "C+"},
{x: 80, grade: "B-"},
{x: 83, grade: "B"},
{x: 87, grade: "B+"},
{x: 90, grade: "A-"},
{x: 93, grade: "A"},
{x: 100, grade: "A+"},
]
Insert cell
// a trick to move the legend below the chart and flip the image
{
d3.select(chart).select("div").raise();
d3.select(chart).select("svg").selectAll("image")
.attr("transform","scale(-1,1)")
.attr("x", -width+20)
.attr("y", height-240)
}
Insert cell
width = 928
Insert cell
height = 600
Insert cell
courseEnds = "2023-12-14T0000"
Insert cell
courseStarts = "2023-08-24T0000"
Insert cell
forecastFactor = 100/percentCourseComplete
Insert cell
groupProjectMin = 0
Insert cell
groupProjectMax = 15
Insert cell
files = ({
"2023-09-25T1615": FileAttachment("2023-09-25T1615_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-10-16T1934": FileAttachment("2023-10-16T1934_Grades-2304046___Data_Visualization@3.csv").csv({typed: true}),
"2023-10-19T1146": FileAttachment("2023-10-19T1146_Grades-2304046___Data_Visualization@2.csv").csv({typed: true}),
"2023-10-23T2204": FileAttachment("2023-10-23T2204_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-10-26T1507": FileAttachment("2023-10-26T1507_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-10-30T1606": FileAttachment("2023-10-30T1606_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-11-02T1417": FileAttachment("2023-11-02T1417_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-11-12T1840": FileAttachment("2023-11-12T1840_Grades-2304046___Data_Visualization@1.csv").csv({typed: true}),
"2023-11-13T1605": FileAttachment("2023-11-13T1605_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-11-20T2018": FileAttachment("2023-11-20T2018_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-11-22T2359": FileAttachment("2023-11-22T2359_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-11-24T0017": FileAttachment("2023-11-24T0017_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-11-27T2042": FileAttachment("2023-11-27T2042_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-12-06T0042": FileAttachment("2023-12-06T0042_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-12-07T1222": FileAttachment("2023-12-07T1222_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-12-11T0917": FileAttachment("2023-12-11T0917_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-12-15T1401": FileAttachment("2023-12-15T1401_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2023-12-18T1927": FileAttachment("2023-12-18T1927_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
"2024-01-07T2235": FileAttachment("2024-01-07T2235_Grades-2304046___Data_Visualization.csv").csv({typed: true}),
})
Insert cell
timeformat = "%Y-%m-%dT%H%M"
Insert cell
timepoints = Object.keys(files).map(m => d3.utcParse(timeformat)(m))
Insert cell
loadPromise = Promise.all(Object.values(files))
Insert cell
data = loadPromise[Object.keys(files).indexOf(time)]
Insert cell
allAssignmnets = data.columns
.filter(f => excludeColumnsContaining.every(s => !f.includes(s)) && excludeColumnsEqualTo.every(s => f !== s))
Insert cell
excludeColumnsContaining = [" Score", " ID", " Grade"]
Insert cell
excludeColumnsEqualTo = ["ID", "Student", "Section"]
Insert cell
tidydata = {
const data2 = data.slice(2)
//remove "test student"
.filter(f => f["Student"] !== ("Student, Test") && f["Student"] !== ("student, Test"))
//remove unnecessary columns
.map(m => filterObjectProps(m, allAssignmnets))
//add a total score
.map((m, i) => {m.total = d3.sum(Object.values(m).map(m => m || 0)); return m;})
//add a randon student ID
.map((m, i) => {m.randomID = "ID" + i + Math.round(Math.random()*100); return m;})

//unpivot the data table (derp derp, there should be a better way to do that)
const result = []
for (let student of data2) {
for (let assignment of Object.keys(student).filter(f => f!=="studentID")) {
result.push({student: student.randomID, assignment, points: student[assignment]})
}
}
return result
}
Insert cell
d3.select(chart)
.append("svg:style")
.text(`
rect:hover {stroke-width: 3px; stroke: black;}
`)
Insert cell
colorRange = allAssignmnets.map(m => m.slice(0,2))
.map(m => {
switch (m) {
case "⚪️": return "#aaa";
case "🟢": return "#41f71d";
case "🟠": return "#ff9838";
case "⚫️": return "#222";
case "🟤": return "#996100";
case "🔴": return "#ff1a1a";
case "🟣": return "#a64dff";
case "🔵": return "#3d77ff";
}
})
Insert cell
filterObjectProps = function(raw, allowed) {
return Object.keys(raw)
.filter(key => allowed.includes(key))
.reduce((obj, key) => {
obj[key] = raw[key];
return obj;
}, {});
}
Insert cell
import {on} from "@fil/plot-onclick-experimental-plugin"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more