Skip to content

Commit 7cea867

Browse files
committed
feat: enhance filter functionality and tooltip clarity in Graphviz components
- Updated FilterComponent to accept and display the current filter state, improving user interaction. - Modified GraphvizParent to handle filtering logic, ensuring accurate edge count calculations based on the selected filter. - Enhanced tooltips in GraphvizProcessing to provide clearer statistics, including refined formatting for better readability. These changes improve the overall user experience by providing more intuitive filtering options and clearer visual data representation.
1 parent 8ad14e1 commit 7cea867

4 files changed

Lines changed: 58 additions & 39 deletions

File tree

src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ function App() {
140140
{/* Filter Section */}
141141
<div className="space-y-2">
142142
<h3 className="text-lg font-semibold text-gray-900">Filters</h3>
143-
<FilterComponent onFilterChange={setFilter} />
143+
<FilterComponent onFilterChange={setFilter} currentFilter={filter} />
144144
</div>
145145

146146
{/* Sequence Section */}
@@ -194,7 +194,7 @@ function App() {
194194
</div>
195195
</div>
196196
<p className="text-xs text-gray-500">
197-
Maximum threshold before any node becomes disconnected: {maxMinEdgeCount} students
197+
Maximum threshold before any node becomes disconnected: {maxMinEdgeCount-1} students
198198
</p>
199199
</div>
200200
</div>

src/components/FilterComponent.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,28 @@ import {
77
SelectTrigger,
88
SelectValue,
99
} from "@/components/ui/select"
10+
1011
interface FilterComponentProps {
1112
onFilterChange: (filter: string) => void;
13+
currentFilter: string;
1214
}
13-
const FilterComponent: React.FC<FilterComponentProps> = ({ onFilterChange }) => {
15+
16+
const FilterComponent: React.FC<FilterComponentProps> = ({ onFilterChange, currentFilter }) => {
1417
return (
1518
<div className="space-y-2">
16-
<Select onValueChange={(value) => onFilterChange(value)}>
17-
<SelectTrigger>
18-
<SelectValue placeholder="Filter by Status" />
19-
</SelectTrigger>
20-
<SelectContent>
21-
<SelectGroup>
22-
<SelectItem value="ALL">All Statuses</SelectItem>
23-
<SelectItem value="GRADUATED">Graduated</SelectItem>
24-
<SelectItem value="PROMOTED">Promoted</SelectItem>
25-
</SelectGroup>
26-
</SelectContent>
27-
</Select>
28-
</div>
19+
<Select value={currentFilter} onValueChange={(value) => onFilterChange(value)}>
20+
<SelectTrigger>
21+
<SelectValue placeholder="Filter by Status" />
22+
</SelectTrigger>
23+
<SelectContent>
24+
<SelectGroup>
25+
<SelectItem value="ALL">All Statuses</SelectItem>
26+
<SelectItem value="GRADUATED">Graduated</SelectItem>
27+
<SelectItem value="PROMOTED">Promoted</SelectItem>
28+
</SelectGroup>
29+
</SelectContent>
30+
</Select>
31+
</div>
2932
);
3033
};
3134

src/components/GraphvizParent.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import '../GraphvizContainer.css';
1515
import { Context } from "@/Context.tsx";
1616
import { Button } from './ui/button';
1717

18+
const titleCase = (str: string | null) => str ? str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() : '';
19+
1820
interface GraphvizParentProps {
1921
csvData: string;
2022
filter: string | null;
@@ -66,7 +68,18 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
6668
const sequenceToUse = selectedSequence || topSequences[0]?.sequence;
6769
if (sequenceToUse) {
6870
const maxMinEdgeCount = calculateMaxMinEdgeCount(edgeCounts, sequenceToUse);
69-
onMaxMinEdgeCountChange(maxMinEdgeCount);
71+
72+
// If filter is active and not 'ALL', compare with filtered graph's minimum edge count
73+
if (filter && filter !== 'ALL') {
74+
const filteredData = sortedData.filter(row => row['CF (Workspace Progress Status)'] === filter);
75+
const filteredStepSequences = createStepSequences(filteredData, selfLoops);
76+
const filteredOutcomeSequences = createOutcomeSequences(filteredData);
77+
const { edgeCounts: filteredEdgeCounts } = countEdges(filteredStepSequences, filteredOutcomeSequences);
78+
const filteredMinEdgeCount = calculateMaxMinEdgeCount(filteredEdgeCounts, sequenceToUse);
79+
onMaxMinEdgeCountChange(Math.min(maxMinEdgeCount, filteredMinEdgeCount));
80+
} else {
81+
onMaxMinEdgeCountChange(maxMinEdgeCount);
82+
}
7083
}
7184

7285
if (JSON.stringify(top5Sequences) !== JSON.stringify(topSequences) || top5Sequences === null) {
@@ -110,10 +123,11 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
110123
)
111124
);
112125
}
113-
}, [csvData, selfLoops, minVisits, selectedSequence, setTop5Sequences, top5Sequences, onMaxEdgeCountChange, onMaxMinEdgeCountChange]);
126+
}, [csvData, selfLoops, minVisits, selectedSequence, setTop5Sequences, top5Sequences, onMaxEdgeCountChange, onMaxMinEdgeCountChange, filter]);
114127

128+
// Add back the useEffect for filtered graph display
115129
useEffect(() => {
116-
if (filter) {
130+
if (filter && filter !== 'ALL') {
117131
const sortedData = loadAndSortData(csvData);
118132
const filteredData = sortedData.filter(row => row['CF (Workspace Progress Status)'] === filter);
119133
const filteredStepSequences = createStepSequences(filteredData, selfLoops);
@@ -241,10 +255,12 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
241255
}, [dotString, numberOfGraphs]);
242256

243257
useEffect(() => {
244-
if (filter && filteredDotString && graphRefFiltered.current) {
258+
if (filter && filter !== 'ALL' && filteredDotString && graphRefFiltered.current) {
245259
renderGraph(filteredDotString, graphRefFiltered, 'filtered_graph', numberOfGraphs);
260+
} else if (filter === 'ALL' || !filter) {
261+
setFilteredDotString(null);
246262
}
247-
}, [filteredDotString, numberOfGraphs]);
263+
}, [filteredDotString, numberOfGraphs, filter]);
248264

249265

250266
return (
@@ -268,10 +284,10 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
268284
className="w-full h-[575px] border-2 border-gray-700 rounded-lg p-4 bg-white"></div>
269285
<ExportButton onClick={() => exportGraphAsPNG(graphRefMain, 'all_students')} /></div>
270286
)}
271-
{filteredDotString && (
287+
{filter && filter !== 'ALL' && filteredDotString && (
272288
<div
273289
className={`graph-item flex flex-col items-center ${topDotString && dotString && filteredDotString ? 'w-[400px]' : 'w-[500px]'} border-2 border-gray-700 rounded-lg p-4 bg-gray-100`}>
274-
<h2 className="text-lg font-semibold text-center mb-4">Filtered Graph</h2>
290+
<h2 className="text-lg font-semibold text-center mb-4">Filtered Graph: {titleCase(filter)}</h2>
275291
<div ref={graphRefFiltered}
276292
className="w-full h-[575px] border-2 border-gray-700 rounded-lg p-4 bg-white"></div>
277293
<ExportButton onClick={() => exportGraphAsPNG(graphRefFiltered, 'filtered_graph')} />

src/components/GraphvizProcessing.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -404,31 +404,31 @@ export function generateDotString(
404404
const totalCount = totalNodeEdges[currentStep] || 0;
405405
const color = calculateColor(rank, totalSteps);
406406
const edgeColor = calculateEdgeColors(outcomes);
407-
const nodeTooltip = `Rank:\n\t\t ${rank + 1}\nColor:\n\t\t ${color}\nTotal Students:\n\t\t${totalNodeEdges[currentStep] || 0}`;
407+
const nodeTooltip = `Rank:\n\t\t${rank + 1}\nColor:\n\t\t${color}\nTotal Students:\n\t\t${totalNodeEdges[currentStep] || 0}`;
408408

409409
dotString += ` "${currentStep}" [rank=${rank + 1}, style=filled, fillcolor="${color}", tooltip="${nodeTooltip}"];\n`;
410410

411411
if (edgeCount > minVisits) {
412412
let tooltip = `${currentStep} to ${nextStep}\n\n`
413413
+ `Student Statistics:\n`
414414
+ `- Total Students at ${currentStep}: \n\t\t${totalCount}\n`
415-
+ `- Unique Students on this path: \n\t\t ${edgeCount}\n`
416-
+ `- Total Edge Visits: \n\t\t ${visits}\n`;
415+
+ `- Unique Students on this path: \n\t\t${edgeCount}\n`
416+
+ `- Total Edge Visits: \n\t\t${visits}\n`;
417417

418418
// Add repeat visit information for all edges
419419
if (repeatVisits[edgeKey]) {
420420
const repeatCounts = Object.values(repeatVisits[edgeKey]);
421421
const studentsWithRepeats = repeatCounts.filter(count => count > 1).length;
422422
const maxRepeats = Math.max(...repeatCounts);
423-
tooltip += `- Students who repeated this path: \n\t\t ${studentsWithRepeats}\n`
424-
+ `- Maximum visits by a student: \n\t\t ${maxRepeats}\n`;
423+
tooltip += `- Students who repeated this path: \n\t\t${studentsWithRepeats}\n`
424+
+ `- Maximum visits by a student: \n\t\t${maxRepeats}\n`;
425425
}
426426

427427
tooltip += `\nPath Statistics:\n`
428428
+ `- Ratio: \n\t\t${((ratioEdges[edgeKey] || 0) * 100).toFixed(2)}% of students at ${currentStep} go to ${nextStep}\n`
429-
+ `- Outcomes: \n\t\t ${Object.entries(outcomes).map(([outcome, count]) => `${outcome}: ${count}`).join('\n\t\t ')}\n`
429+
+ `- Outcomes: \n\t\t${Object.entries(outcomes).map(([outcome, count]) => `${outcome}: ${count}`).join('\n\t\t')}\n`
430430
+`\nVisual Properties:\n`
431-
+ `- Color: \n\t\t Hex: ${color}\n`;
431+
+ `- Color: \n\t\tHex: ${color}\n`;
432432

433433
dotString += ` "${currentStep}" -> "${nextStep}" [penwidth=${thickness}, color="${edgeColor}", tooltip="${tooltip}"];\n`;
434434
}
@@ -437,7 +437,7 @@ export function generateDotString(
437437
for (let rank = 0; rank < totalSteps; rank++) {
438438
const currentStep = steps[rank];
439439
const color = calculateColor(rank, totalSteps);
440-
const nodeTooltip = `Rank:\n\t\t ${rank + 1}\nColor:\n\t\t ${color}\nTotal Students:\n\t\t${totalNodeEdges[currentStep] || 0}`;
440+
const nodeTooltip = `Rank:\n\t\t${rank + 1}\nColor:\n\t\t${color}\nTotal Students:\n\t\t${totalNodeEdges[currentStep] || 0}`;
441441

442442
dotString += ` "${currentStep}" [rank=${rank + 1}, style=filled, fillcolor="${color}", tooltip="${nodeTooltip}"];\n`;
443443
}
@@ -453,30 +453,30 @@ export function generateDotString(
453453
const color = calculateEdgeColors(outcomes);
454454
const outcomesStr = Object.entries(outcomes)
455455
.map(([outcome, count]) => `${outcome}: ${count}`)
456-
.join('\n\t\t ');
456+
.join('\n\t\t');
457457

458458
if (edgeCount > minVisits) {
459459
let tooltip = `${currentStep} to ${nextStep}\n\n`
460460
+ `Student Statistics:\n`
461461
+ `- Total Students at ${currentStep}: \n\t\t${totalCount || 0}\n`
462-
+ `- Unique Students on this path: \n\t\t ${edgeCount}\n`
463-
+ `- Total Edge Visits: \n\t\t ${visits}\n`;
462+
+ `- Unique Students on this path: \n\t\t${edgeCount}\n`
463+
+ `- Total Edge Visits: \n\t\t${visits}\n`;
464464

465465
// Add repeat visit information for all edges
466466
if (repeatVisits[edge]) {
467467
const repeatCounts = Object.values(repeatVisits[edge]);
468468
const studentsWithRepeats = repeatCounts.filter(count => count > 1).length;
469469
const maxRepeats = Math.max(...repeatCounts);
470-
tooltip += `- Students who repeated this path: \n\t\t ${studentsWithRepeats}\n`
471-
+ `- Maximum visits by a student: \n\t\t ${maxRepeats}\n`;
470+
tooltip += `- Students who repeated this path: \n\t\t${studentsWithRepeats}\n`
471+
+ `- Maximum visits by a student: \n\t\t${maxRepeats}\n`;
472472
}
473473

474474
tooltip += `\nPath Statistics:\n`
475475
+ `- Ratio: \n\t\t${((ratioEdges[edge] || 0) * 100).toFixed(2)}% of students at ${currentStep} go to ${nextStep}\n`
476-
+ `- Outcomes: \n\t\t ${outcomesStr}\n`
476+
+ `- Outcomes: \n\t\t${outcomesStr}\n`
477477
+ `\nVisual Properties:\n`
478-
+ `- Color: \n\t\t Hex: ${color}\n`
479-
+ `\t\t RGB: ${[parseInt(color.substring(1, 3), 16), parseInt(color.substring(3, 5), 16), parseInt(color.substring(5, 7), 16)]}`;
478+
+ `- Color: \n\t\tHex: ${color}\n`
479+
+ `\t\tRGB: ${[parseInt(color.substring(1, 3), 16), parseInt(color.substring(3, 5), 16), parseInt(color.substring(5, 7), 16)]}`;
480480

481481
dotString += ` "${currentStep}" -> "${nextStep}" [penwidth=${thickness}, color="${color}", tooltip="${tooltip}"];\n`;
482482
}

0 commit comments

Comments
 (0)