Skip to content

Commit 8ad14e1

Browse files
committed
feat: enhance minimum visits configuration and add edge count calculation
- Introduced a new input field for setting the minimum number of students required for edge visibility, allowing for more precise control over graph display. - Updated the App component to calculate the minimum visits percentage based on the new input and maximum edge count. - Added a new function, calculateMaxMinEdgeCount, to determine the minimum number of students on any edge in the selected sequence. - Modified GraphvizParent to handle the new maximum minimum edge count and update the parent component accordingly. These changes improve the configurability of the graph visualization and provide users with better insights into the data.
1 parent 16defa1 commit 8ad14e1

5 files changed

Lines changed: 137 additions & 26 deletions

File tree

src/App.tsx

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import './App.css';
2-
import { useContext, useMemo, useState } from 'react';
2+
import { useContext, useMemo, useState, useEffect } from 'react';
33
import { Button } from './components/ui/button';
44
import Upload from "@/components/Upload.tsx";
55
import GraphvizParent from "@/components/GraphvizParent.tsx";
@@ -13,6 +13,7 @@ import {
1313
PopoverContent,
1414
PopoverTrigger,
1515
} from "@/components/ui/popover"
16+
import { Input } from "@/components/ui/input"
1617

1718
import Loading from './components/Loading.tsx';
1819

@@ -24,9 +25,18 @@ function App() {
2425
// State to toggle whether self-loops (transitions back to the same node) should be included
2526
const [selfLoops, setSelfLoops] = useState<boolean>(true);
2627
// State to manage the minimum number of visits for displaying edges in the graph
27-
const [minVisitsPercentage, setMinVisitsPercentage] = useState<number>(10);
28+
const [minVisitsPercentage, setMinVisitsPercentage] = useState<number>(0);
2829
const { resetData, loading, error, top5Sequences, setSelectedSequence, selectedSequence, csvData, setCSVData } = useContext(Context);
2930
const [maxEdgeCount, setMaxEdgeCount] = useState<number>(100); // Default value
31+
const [maxMinEdgeCount, setMaxMinEdgeCount] = useState<number>(0);
32+
33+
// Update minVisitsPercentage when maxMinEdgeCount changes
34+
useEffect(() => {
35+
if (maxMinEdgeCount > 0) {
36+
const percentage = ((maxMinEdgeCount - 1) / maxEdgeCount) * 100;
37+
setMinVisitsPercentage(Math.max(0, Math.min(100, percentage)));
38+
}
39+
}, [maxMinEdgeCount, maxEdgeCount]);
3040

3141
const showControls = useMemo(() => {
3242
return !loading && csvData.length > 0;
@@ -62,6 +72,19 @@ function App() {
6272
// Calculate actual min visits from percentage
6373
const minVisits = Math.round((minVisitsPercentage / 100) * maxEdgeCount);
6474

75+
/**
76+
* Updates the minimum visits percentage when the input value changes.
77+
*
78+
* @param {string} value - The new value from the input.
79+
*/
80+
const handleInputChange = (value: string) => {
81+
const numValue = parseInt(value);
82+
if (!isNaN(numValue)) {
83+
const percentage = Math.min(100, Math.max(0, (numValue / maxEdgeCount) * 100));
84+
setMinVisitsPercentage(percentage);
85+
}
86+
};
87+
6588
/**
6689
* Updates the loading state when the file upload or processing begins or ends.
6790
*
@@ -137,15 +160,42 @@ function App() {
137160
</div>
138161

139162
<div className="space-y-2">
140-
<label className="text-sm font-medium text-gray-700">Edge Visits</label>
141-
<Slider
142-
step={1}
143-
min={0}
144-
max={100}
145-
value={minVisitsPercentage}
146-
onChange={handleSlider}
147-
maxEdgeCount={maxEdgeCount}
148-
/>
163+
<label className="text-sm font-medium text-gray-700">Minimum Students</label>
164+
<div className="space-y-4">
165+
<div>
166+
<div className="flex justify-between mb-1">
167+
{/* <span className="text-sm text-gray-500">Percentage</span>
168+
<span className="text-sm text-gray-500">{minVisitsPercentage}%</span> */}
169+
</div>
170+
<div className="flex items-center gap-2">
171+
<Slider
172+
step={1}
173+
min={0}
174+
max={100}
175+
value={minVisitsPercentage}
176+
onChange={handleSlider}
177+
maxEdgeCount={maxEdgeCount}
178+
/>
179+
<span className="text-sm text-gray-500">%</span>
180+
</div>
181+
</div>
182+
<div>
183+
<div className="flex justify-between mb-1">
184+
<span className="text-sm text-gray-500">Students</span>
185+
</div>
186+
<Input
187+
type="number"
188+
value={minVisits}
189+
onChange={(e) => handleInputChange(e.target.value)}
190+
className="w-full"
191+
min={0}
192+
max={maxEdgeCount}
193+
/>
194+
</div>
195+
</div>
196+
<p className="text-xs text-gray-500">
197+
Maximum threshold before any node becomes disconnected: {maxMinEdgeCount} students
198+
</p>
149199
</div>
150200
</div>
151201
</div>
@@ -165,6 +215,7 @@ function App() {
165215
selfLoops={selfLoops}
166216
minVisits={minVisits}
167217
onMaxEdgeCountChange={setMaxEdgeCount}
218+
onMaxMinEdgeCountChange={setMaxMinEdgeCount}
168219
/>
169220
</div>
170221
</div>

src/Context.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,29 @@ interface ContextInterface {
1111

1212
loading: boolean;
1313
top5Sequences: SequenceCount[] | null;
14+
f3L3: boolean;
1415
selectedSequence: string[] | undefined; // string[] or SequenceCount[].sequence?
1516
setLoading: (loading: boolean) => void;
1617
setData: (data: GlobalDataType[] | null) => void;
18+
setF3L3: (f3L3: boolean) => void;
1719
setGraphData: (graphData: GraphData | null) => void;
1820
setTop5Sequences: (top5Sequences: SequenceCount[] | null) => void;
1921
setSelectedSequence: (selectedSequence: string[] | undefined) => void;
20-
2122
setCSVData: (csvData: string) => void;
2223

2324
resetData: () => void;
2425

2526
}
26-
27+
export interface CSVRow {
28+
'Session Id'?: string;
29+
'Time': string;
30+
'Step Name': string;
31+
'Outcome': string;
32+
'CF (Workspace Progress Status)': string;
33+
'Problem Name': string;
34+
'Anon Student Id': string;
35+
'first or last'?: string;
36+
}
2737
export interface SequenceCount {
2838
sequence: string[] | undefined;
2939
count: number;//| null;
@@ -37,6 +47,7 @@ const initialState = {
3747
top5Sequences: null,
3848
selectedSequence: undefined,
3949
csvData:'',
50+
f3L3: false,
4051
}
4152

4253
interface ProviderProps {
@@ -51,9 +62,10 @@ export const Provider = ({children}: ProviderProps) => {
5162
const [error, setError] = useState<string | null>(null)
5263

5364
const [csvData, setCSVData] = useState<string>(initialState.csvData)
54-
55-
56-
// const queryClient = useQueryClient();
65+
const [top5Sequences, setTop5Sequences] = useState<SequenceCount[] | null>(initialState.top5Sequences)
66+
const [selectedSequence, setSelectedSequence] = useState<SequenceCount["sequence"] | undefined>(initialState.selectedSequence);
67+
const [f3L3, setF3L3] = useState<boolean | null>(initialState.f3L3)
68+
// const queryClient = useQueryClient();
5769

5870
// const { data: uploadedData } = useQuery<GlobalDataType[]>({
5971
// queryKey: ['uploadedData'],
@@ -68,9 +80,6 @@ export const Provider = ({children}: ProviderProps) => {
6880
// },
6981
// });
7082

71-
const [top5Sequences, setTop5Sequences] = useState<SequenceCount[] | null>(initialState.top5Sequences)
72-
const [selectedSequence, setSelectedSequence] = useState<SequenceCount["sequence"] | undefined>(initialState.selectedSequence);
73-
7483
const resetData = () => {
7584
setData(null)
7685
setCSVData('')
@@ -95,11 +104,13 @@ export const Provider = ({children}: ProviderProps) => {
95104
setError,
96105
setLoading,
97106
setData,
107+
f3L3,
98108
setGraphData,
99109
setTop5Sequences,
100110
setSelectedSequence,
101111
setCSVData,
102112
resetData,
113+
setF3L3
103114
}}
104115
>
105116
{children}

src/components/GraphvizParent.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
countEdges,
88
createStepSequences,
99
createOutcomeSequences,
10-
loadAndSortData
10+
loadAndSortData,
11+
calculateMaxMinEdgeCount
1112
} from './GraphvizProcessing';
1213
import ErrorBoundary from "@/components/errorBoundary.tsx";
1314
import '../GraphvizContainer.css';
@@ -20,14 +21,16 @@ interface GraphvizParentProps {
2021
selfLoops: boolean;
2122
minVisits: number;
2223
onMaxEdgeCountChange: (count: number) => void;
24+
onMaxMinEdgeCountChange: (count: number) => void;
2325
}
2426

2527
const GraphvizParent: React.FC<GraphvizParentProps> = ({
2628
csvData,
2729
filter,
2830
selfLoops,
2931
minVisits,
30-
onMaxEdgeCountChange
32+
onMaxEdgeCountChange,
33+
onMaxMinEdgeCountChange
3134
}) => {
3235
const [dotString, setDotString] = useState<string | null>(null);
3336
const [filteredDotString, setFilteredDotString] = useState<string | null>(null);
@@ -58,11 +61,18 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
5861

5962
// Update the maxEdgeCount in the parent component
6063
onMaxEdgeCountChange(maxEdgeCount);
64+
65+
// Calculate and update the maximum minimum-edge count
66+
const sequenceToUse = selectedSequence || topSequences[0]?.sequence;
67+
if (sequenceToUse) {
68+
const maxMinEdgeCount = calculateMaxMinEdgeCount(edgeCounts, sequenceToUse);
69+
onMaxMinEdgeCountChange(maxMinEdgeCount);
70+
}
6171

6272
if (JSON.stringify(top5Sequences) !== JSON.stringify(topSequences) || top5Sequences === null) {
6373
setTop5Sequences(topSequences);
6474
if (topSequences && selectedSequence === undefined) {
65-
setSelectedSequence(topSequences![0].sequence);
75+
setSelectedSequence(topSequences[0].sequence);
6676
}
6777
}
6878

@@ -77,7 +87,7 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
7787
totalNodeEdges,
7888
1,
7989
minVisits,
80-
selectedSequence,
90+
selectedSequence || topSequences[0].sequence,
8191
false,
8292
totalVisits,
8393
repeatVisits
@@ -93,14 +103,14 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
93103
totalNodeEdges,
94104
1,
95105
minVisits,
96-
selectedSequence,
106+
selectedSequence || topSequences[0].sequence,
97107
true,
98108
totalVisits,
99109
repeatVisits
100110
)
101111
);
102112
}
103-
}, [csvData, selfLoops, minVisits, selectedSequence, setTop5Sequences, top5Sequences, onMaxEdgeCountChange]);
113+
}, [csvData, selfLoops, minVisits, selectedSequence, setTop5Sequences, top5Sequences, onMaxEdgeCountChange, onMaxMinEdgeCountChange]);
104114

105115
useEffect(() => {
106116
if (filter) {

src/components/GraphvizProcessing.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,4 +486,43 @@ export function generateDotString(
486486

487487
dotString += '}';
488488
return dotString;
489+
}
490+
491+
/**
492+
* Calculates the minimum number of students on any edge in the selected sequence.
493+
* This is the smallest number of unique students that traverse any edge in the sequence.
494+
* @param edgeCounts - Dictionary mapping edge keys to the number of unique students
495+
* @param selectedSequence - The selected sequence of steps
496+
* @returns The minimum number of students on any edge in the sequence
497+
*/
498+
export function calculateMaxMinEdgeCount(
499+
edgeCounts: { [key: string]: number },
500+
selectedSequence: string[]
501+
): number {
502+
if (!selectedSequence || selectedSequence.length < 2) {
503+
console.log("No valid sequence provided");
504+
return 0;
505+
}
506+
507+
let minStudentCount = Infinity;
508+
console.log("Edge counts:", edgeCounts);
509+
console.log("Selected sequence:", selectedSequence);
510+
511+
// Check each edge in the sequence
512+
for (let i = 0; i < selectedSequence.length - 1; i++) {
513+
const currentStep = selectedSequence[i];
514+
const nextStep = selectedSequence[i + 1];
515+
const edgeKey = `${currentStep}->${nextStep}`;
516+
517+
// Get the number of unique students on this edge
518+
const studentCount = edgeCounts[edgeKey] || 0;
519+
console.log(`Edge ${edgeKey}: ${studentCount} students`);
520+
521+
if (studentCount < minStudentCount) {
522+
minStudentCount = studentCount;
523+
}
524+
}
525+
526+
console.log("Final min student count:", minStudentCount);
527+
return minStudentCount === Infinity ? 0 : minStudentCount;
489528
}

src/components/slider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const Slider: React.FC<SliderProps> = ({min, max, step = 1, value, onChange, max
2828
return (
2929
<div className="space-y-2">
3030
<label className="block text-sm font-medium text-gray-700">
31-
Minimum Edge Visits: {value}% ({actualStudents} students)
31+
Minimum Students: {actualStudents} ({value.toFixed(2)}% of {maxEdgeCount})
3232
</label>
3333
<div className="flex items-center space-x-4">
3434
<input

0 commit comments

Comments
 (0)