From 7679a63d53b4185a6ae9105b0ba22754aefd2471 Mon Sep 17 00:00:00 2001 From: jakeaturner Date: Tue, 16 Jul 2024 20:48:08 -0700 Subject: [PATCH] fix: various minor ui improvements --- components/EarlyWarningCourseMetrics.tsx | 6 +-- components/EarlyWarningStudentRow.tsx | 22 ++++++----- components/InstructorDashboard.tsx | 14 +++---- components/InstructorQuickMetrics.tsx | 8 ++-- components/LearningCurveDescriptor.tsx | 37 ++++++++++++++++++- components/LearningObjectiveLevel.tsx | 8 ++-- components/SmallMetricCard.tsx | 4 +- components/Visualizations/StudentActivity.tsx | 30 +++++++++++++++ components/Visualizations/TimeInReview.tsx | 2 +- components/Visualizations/TimeOnTask.tsx | 2 +- 10 files changed, 98 insertions(+), 35 deletions(-) diff --git a/components/EarlyWarningCourseMetrics.tsx b/components/EarlyWarningCourseMetrics.tsx index bb8e781..817e799 100644 --- a/components/EarlyWarningCourseMetrics.tsx +++ b/components/EarlyWarningCourseMetrics.tsx @@ -10,15 +10,13 @@ const EarlyWarningCourseMetrics = ({ return (
diff --git a/components/EarlyWarningStudentRow.tsx b/components/EarlyWarningStudentRow.tsx index 4bbcf65..9e0b45f 100644 --- a/components/EarlyWarningStudentRow.tsx +++ b/components/EarlyWarningStudentRow.tsx @@ -25,14 +25,14 @@ const Metric = ({ unit: string; percent: boolean; absolute?: boolean; - color?: "red" | "green"; + color?: "red" | "dark-red" | "black"; }) => { const textColorClass = color === "red" ? "tw-text-red-500" - : color === "green" - ? "tw-text-black" - : ""; + : color === "dark-red" + ? "tw-text-red-700" + : "tw-text-black"; return (
@@ -93,18 +93,20 @@ const EarlyWarningStudentRow: React.FC = ({ value={data.estimated_final} unit="Predicted Final Score" percent={true} - color={data.estimated_final > 70 ? "green" : "red"} + color={ + data.estimated_final < 50 + ? "dark-red" + : data.estimated_final < 70 + ? "red" + : "black" + } /> - +
diff --git a/components/InstructorDashboard.tsx b/components/InstructorDashboard.tsx index e0badd9..ecc6505 100644 --- a/components/InstructorDashboard.tsx +++ b/components/InstructorDashboard.tsx @@ -30,11 +30,11 @@ const InstructorDashboard = ({ @@ -45,8 +45,8 @@ const InstructorDashboard = ({ /> @@ -63,7 +63,7 @@ const InstructorDashboard = ({ */} */} diff --git a/components/InstructorQuickMetrics.tsx b/components/InstructorQuickMetrics.tsx index 2f4beeb..47cbed5 100644 --- a/components/InstructorQuickMetrics.tsx +++ b/components/InstructorQuickMetrics.tsx @@ -17,13 +17,13 @@ const InstructorQuickMetrics = ({ course_id }: { course_id: string }) => { @@ -31,7 +31,7 @@ const InstructorQuickMetrics = ({ course_id }: { course_id: string }) => { @@ -39,7 +39,7 @@ const InstructorQuickMetrics = ({ course_id }: { course_id: string }) => { diff --git a/components/LearningCurveDescriptor.tsx b/components/LearningCurveDescriptor.tsx index 4b7eadb..744b2d3 100644 --- a/components/LearningCurveDescriptor.tsx +++ b/components/LearningCurveDescriptor.tsx @@ -64,14 +64,17 @@ const LearningCurveDescriptor: React.FC = ({ return calculated > DEFAULT_MAX ? calculated : DEFAULT_MAX; }; + const xMax = maxAttempts(); + const yMax = maxScore(); + const x = d3 .scaleLinear() - .domain([0, maxAttempts()]) + .domain([0, xMax]) .range([MARGIN.left, width - MARGIN.right]); const y = d3 .scaleLinear() - .domain([0, maxScore()]) + .domain([0, yMax]) .range([height - MARGIN.bottom, MARGIN.top]); svg @@ -97,6 +100,36 @@ const LearningCurveDescriptor: React.FC = ({ .attr("transform", `translate(${MARGIN.left}, 0)`) .call(d3.axisLeft(y)); + // Calculate the regression line + const n = cleaned.length; + const sumX = cleaned.reduce((sum, d) => sum + d.num_attempts, 0); + const sumY = cleaned.reduce((sum, d) => sum + d.score, 0); + const sumXY = cleaned.reduce((sum, d) => sum + d.num_attempts * d.score, 0); + const sumXX = cleaned.reduce( + (sum, d) => sum + d.num_attempts * d.num_attempts, + 0 + ); + + const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); + const intercept = (sumY - slope * sumX) / n; + + // Define the regression line data + const regressionLine = [ + { num_attempts: 0, score: intercept }, + { num_attempts: xMax, score: slope * xMax + intercept }, + ]; + + // Add the regression line to the SVG + svg + .append("line") + .datum(regressionLine) + .attr("x1", (d) => x(d[0].num_attempts)) + .attr("y1", (d) => y(d[0].score)) + .attr("x2", (d) => x(d[1].num_attempts)) + .attr("y2", (d) => y(d[1].score)) + .attr("stroke", "red") + .attr("stroke-width", 2); + setLoading(false); } diff --git a/components/LearningObjectiveLevel.tsx b/components/LearningObjectiveLevel.tsx index 28dd87c..18d7033 100644 --- a/components/LearningObjectiveLevel.tsx +++ b/components/LearningObjectiveLevel.tsx @@ -11,7 +11,10 @@ import { } from "react-bootstrap-icons"; import LearningObjectiveQuestionsAligned from "./LearningObjectiveQuestionsAligned"; -const MARGIN = DEFAULT_MARGINS; +const MARGIN = { + ...DEFAULT_MARGINS, + bottom: 50, +}; const DEFAULT_HEIGHT = 150; interface LearningObjectiveLevelProps { @@ -144,9 +147,6 @@ const LearningObjectiveLevel: React.FC = ({ .append("g") .call(d3.axisBottom(x).ticks(5)) .attr("transform", `translate(0, 0)`); - - // remove the text from the axis - group.append("g").call(d3.axisLeft(y)).selectAll("text").remove(); }); setLoading(false); diff --git a/components/SmallMetricCard.tsx b/components/SmallMetricCard.tsx index e83f659..e30b2da 100644 --- a/components/SmallMetricCard.tsx +++ b/components/SmallMetricCard.tsx @@ -4,7 +4,7 @@ import classNames from "classnames"; interface SmallMetricCardProps { title: string; value?: number | string; - unit: string; + unit?: string; className?: string; loading?: boolean; } @@ -40,7 +40,7 @@ const SmallMetricCard: React.FC = ({ : new Intl.NumberFormat().format(value)}

)} -

{unit}

+ {unit &&

{unit}

} ); }; diff --git a/components/Visualizations/StudentActivity.tsx b/components/Visualizations/StudentActivity.tsx index 138472d..ef0c5c2 100644 --- a/components/Visualizations/StudentActivity.tsx +++ b/components/Visualizations/StudentActivity.tsx @@ -130,6 +130,11 @@ const StudentActivity: React.FC = ({ .scaleOrdinal() .domain(subgroups) .range(["#1f77b4", "#ff7f0e"]); + + const labelScale = d3 + .scaleOrdinal() + .domain(subgroups) + .range([0, 1]); const pie = d3.pie().value((d: any) => { return d[1]; @@ -142,6 +147,10 @@ const StudentActivity: React.FC = ({ ) as any ); const arcGenerator = d3.arc().innerRadius(0).outerRadius(radius); + const labelArcGenerator = d3 + .arc() + .innerRadius(radius * 0.5) + .outerRadius(radius * 0.5); svg .selectAll("slices") @@ -157,6 +166,27 @@ const StudentActivity: React.FC = ({ .style("opacity", 0.7) .attr("transform", `translate(${width / 2}, ${height / 2})`); + // Add labels + svg + .selectAll("slices") + .data(dataReady) + .enter() + .append("text") + // @ts-ignore + .text((d) => labelScale(d.data) === 0 ? "Submitted" : unsubmitted.length > 0 ? "Not Submitted" : "") // Only show "Not Submitted" if there are unsubmitted questions + // @ts-ignore + .attr("transform", (d) => `translate(${labelArcGenerator.centroid(d)})`) + .style("text-anchor", "middle") + .style("font-size", "12px") + .style("font-weight", "semibold") + .attr("transform", (d) => { + // @ts-ignore + const pos = labelArcGenerator.centroid(d); + pos[0] = pos[0] * 2; // Change the 1.5 to another value to move the label further or closer + pos[1] = pos[1] * 1.5; + return `translate(${pos}) translate(${width / 2}, ${height / 2})`; + }); + if (unsubmitted.length > 0) { // Add "Not Submitted" to the legend svg diff --git a/components/Visualizations/TimeInReview.tsx b/components/Visualizations/TimeInReview.tsx index 67264bb..aa589f5 100644 --- a/components/Visualizations/TimeInReview.tsx +++ b/components/Visualizations/TimeInReview.tsx @@ -199,7 +199,7 @@ const TimeInReview: React.FC = ({ .attr("text-anchor", "middle") .style("font-size", "12px") .style("font-weight", "semibold") - .text(`Assignment: ${getName(selectedAssignmentId)}`); + .text(`Questions in Assignment: ${getName(selectedAssignmentId)}`); // Add one dot in the legend for each name. svg diff --git a/components/Visualizations/TimeOnTask.tsx b/components/Visualizations/TimeOnTask.tsx index d30b3cf..4085235 100644 --- a/components/Visualizations/TimeOnTask.tsx +++ b/components/Visualizations/TimeOnTask.tsx @@ -201,7 +201,7 @@ const TimeOnTask: React.FC = ({ .attr("text-anchor", "middle") .style("font-size", "12px") .style("font-weight", "semibold") - .text(`Assignment: ${getName(selectedAssignmentId)}`); + .text(`Questions in Assignment: ${getName(selectedAssignmentId)}`); // Add one dot in the legend for each name. svg