Skip to content

Commit 45cd05a

Browse files
Update dependencies and add new UI components
1 parent 01b1b3f commit 45cd05a

11 files changed

Lines changed: 542 additions & 101 deletions

bun.lockb

17.4 KB
Binary file not shown.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
},
1313
"dependencies": {
1414
"@radix-ui/react-alert-dialog": "^1.0.5",
15+
"@radix-ui/react-dropdown-menu": "^2.1.4",
1516
"@radix-ui/react-hover-card": "^1.0.7",
1617
"@radix-ui/react-icons": "^1.3.0",
1718
"@radix-ui/react-label": "^2.1.1",
1819
"@radix-ui/react-navigation-menu": "^1.1.4",
20+
"@radix-ui/react-popover": "^1.1.4",
1921
"@radix-ui/react-radio-group": "^1.1.3",
22+
"@radix-ui/react-select": "^2.1.4",
2023
"@radix-ui/react-separator": "^1.1.1",
2124
"@radix-ui/react-slider": "^1.1.2",
2225
"@radix-ui/react-slot": "^1.0.2",

src/App.tsx

Lines changed: 55 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import './App.css';
2-
import { useContext, useEffect, useMemo, useState } from 'react';
3-
import DropZone from './components/DropZone';
2+
import { useContext, useMemo, useState } from 'react';
43
import { Button } from './components/ui/button';
54
import Upload from "@/components/Upload.tsx";
65
import GraphvizParent from "@/components/GraphvizParent.tsx";
76
import FilterComponent from './components/FilterComponent.tsx';
87
import SelfLoopSwitch from './components/selfLoopSwitch.tsx';
9-
import Slider from './components/slider.tsx';
8+
import Slider from '@/components/slider.tsx';
109
import SequenceSelector from "@/components/SequenceSelector.tsx";
1110
import { Context, SequenceCount } from "@/Context.tsx";
12-
import { Separator } from "@/components/ui/separator"
11+
import {
12+
Popover,
13+
PopoverContent,
14+
PopoverTrigger,
15+
} from "@/components/ui/popover"
16+
1317
import Loading from './components/Loading.tsx';
1418

1519
function App() {
@@ -21,8 +25,7 @@ function App() {
2125
const [selfLoops, setSelfLoops] = useState<boolean>(true);
2226
// State to manage the minimum number of visits for displaying edges in the graph
2327
const [minVisits, setMinVisits] = useState<number>(0);
24-
const [menuVisible, setMenuVisible] = useState(false);
25-
const { resetData, setGraphData, data, setData, loading, error, setError, top5Sequences, setSelectedSequence, setLoading, selectedSequence, csvData, setCSVData } = useContext(Context);
28+
const { resetData, loading, error, setError, top5Sequences, setSelectedSequence, selectedSequence, csvData, setCSVData } = useContext(Context);
2629
const showControls = useMemo(() => {
2730
if (loading == false && csvData.length > 0) {
2831
return true;
@@ -46,7 +49,6 @@ function App() {
4649
setError(errorMessage);
4750
}
4851

49-
const toggleMenu = () => setMenuVisible(!menuVisible);
5052
/**
5153
* Toggles the self-loops inclusion in the graph by switching the state.
5254
*/
@@ -71,7 +73,7 @@ function App() {
7173
*
7274
* @param {boolean} loading - Whether the data is currently loading/processing.
7375
*/
74-
76+
7577
// Rendering the components that allow user interaction and display the graph
7678
return (
7779
<div className='p-3'>
@@ -88,8 +90,8 @@ function App() {
8890
</div>
8991
</div>
9092
</header>
91-
{!showControls && <Upload onDataProcessed={handleDataProcessed} />}
92-
93+
{!showControls && <Upload onDataProcessed={handleDataProcessed} />}
94+
9395
{loading && <Loading />}
9496
{/* Display Error Message */}
9597
{error && (
@@ -103,8 +105,8 @@ function App() {
103105

104106
{
105107
showControls && (
106-
<div className="p-5 m-2">
107-
108+
<div className="p-5 m-2 flex flex-col gap-3">
109+
108110
<div className="selected-sequence-bar flex justify-between bg-gray-200 p-4 mb-4">
109111
<h2 className="text-lg font-semibold">Selected Sequence:</h2>
110112
{selectedSequence && (
@@ -114,37 +116,47 @@ function App() {
114116
)}
115117
</div>
116118
{/* Properties Button */}
117-
<button
118-
className="flex-auto top-10 left-10 bg-blue-500 text-white z-30 rounded-md p-4 mb-4"
119-
onClick={toggleMenu}
120-
>
121-
{menuVisible ? "Hide Properties" : "Show Properties"}
122-
</button>
123-
124-
{/* Properties Menu */}
125-
126-
{menuVisible && (
127-
<div className="absolute top-50 left-4 p-4 bg-gray-200 z-30 shadow-lg w-85 rounded-lg">
128-
<h3 className="text-md font-semibold mb-4">Properties Menu</h3>
129-
<FilterComponent onFilterChange={setFilter} />
130-
{/*{selectedSequence && (*/}
131-
{/* <h2>{selectedSequence.toString().split('->').join(' -> ')}</h2>*/}
132-
{/*)}*/}
133-
<SequenceSelector
134-
onSequenceSelect={handleSelectSequence}
135-
sequences={top5Sequences!}
136-
selectedSequence={selectedSequence}
137-
/>
138-
<SelfLoopSwitch isOn={selfLoops} handleToggle={handleToggle} />
139-
<Slider
140-
step={5}
141-
min={0}
142-
max={5000}
143-
value={minVisits}
144-
onChange={handleSlider}
145-
/>
146-
</div>
147-
)}
119+
<Popover>
120+
<PopoverTrigger className="w-fit bg-slate-500 p-3 rounded-lg text-white">Properties</PopoverTrigger>
121+
<PopoverContent className="w-96 bg-white rounded-lg shadow-lg p-6 border border-gray-200 mx-10">
122+
<div className="flex flex-col space-y-6">
123+
{/* Filter Section */}
124+
<div className="space-y-2">
125+
<h3 className="text-lg font-semibold text-gray-900">Filters</h3>
126+
<FilterComponent onFilterChange={setFilter} />
127+
</div>
128+
129+
{/* Sequence Section */}
130+
<div className="space-y-2">
131+
<h3 className="text-lg font-semibold text-gray-900">Sequences</h3>
132+
<SequenceSelector
133+
onSequenceSelect={handleSelectSequence}
134+
sequences={top5Sequences || []}
135+
selectedSequence={selectedSequence}
136+
/>
137+
</div>
138+
139+
{/* Controls Section */}
140+
<div className="space-y-4">
141+
<div className="pb-2 border-b border-gray-200">
142+
<SelfLoopSwitch isOn={selfLoops} handleToggle={handleToggle} />
143+
</div>
144+
145+
<div className="space-y-2">
146+
<label className="text-sm font-medium text-gray-700">Edge Visits</label>
147+
<Slider
148+
step={5}
149+
min={0}
150+
max={5000}
151+
value={minVisits}
152+
onChange={handleSlider}
153+
/>
154+
</div>
155+
</div>
156+
</div>
157+
</PopoverContent>
158+
</Popover>
159+
148160

149161
{/* Graph and Data Display */}
150162
{!loading && csvData && (

src/components/FilterComponent.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
import React from 'react';
2-
2+
import {
3+
Select,
4+
SelectContent,
5+
SelectGroup,
6+
SelectItem,
7+
SelectLabel,
8+
SelectTrigger,
9+
SelectValue,
10+
} from "@/components/ui/select"
311
interface FilterComponentProps {
412
onFilterChange: (filter: string) => void;
513
}
6-
714
const FilterComponent: React.FC<FilterComponentProps> = ({ onFilterChange }) => {
8-
const handleFilterChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
9-
onFilterChange(event.target.value);
10-
};
11-
1215
return (
13-
<div>
14-
<label htmlFor="statusFilter">Filter by Completion Status:</label>
15-
<select id="statusFilter" onChange={handleFilterChange}>
16-
<option value="">All</option>
17-
<option value="GRADUATED">Graduated</option>
18-
<option value="PROMOTED">Promoted</option>
19-
</select>
20-
</div>
16+
<div className="space-y-2">
17+
<Select onValueChange={(value) => onFilterChange(value)}>
18+
<SelectTrigger>
19+
<SelectValue placeholder="Filter by Status" />
20+
</SelectTrigger>
21+
<SelectContent>
22+
<SelectGroup>
23+
<SelectItem value="ALL STATUSES">All Statuses</SelectItem>
24+
<SelectItem value="GRADUATED">Graduated</SelectItem>
25+
<SelectItem value="PROMOTED">Promoted</SelectItem>
26+
</SelectGroup>
27+
</SelectContent>
28+
</Select>
29+
</div>
2130
);
2231
};
2332

src/components/GraphvizParent.tsx

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// React component code
2-
import React, {useContext, useEffect, useRef, useState} from 'react';
3-
import {graphviz} from 'd3-graphviz';
2+
import React, { useContext, useEffect, useRef, useState } from 'react';
3+
import { graphviz } from 'd3-graphviz';
44
import {
55
generateDotString,
66
normalizeThicknesses,
@@ -11,7 +11,8 @@ import {
1111
} from './GraphvizProcessing';
1212
import ErrorBoundary from "@/components/errorBoundary.tsx";
1313
import '../GraphvizContainer.css';
14-
import {Context} from "@/Context.tsx";
14+
import { Context } from "@/Context.tsx";
15+
import { Button } from './ui/button';
1516

1617
interface GraphvizParentProps {
1718
csvData: string;
@@ -21,15 +22,15 @@ interface GraphvizParentProps {
2122
}
2223

2324
const GraphvizParent: React.FC<GraphvizParentProps> = ({
24-
csvData,
25-
filter,
26-
selfLoops,
27-
minVisits,
28-
}) => {
25+
csvData,
26+
filter,
27+
selfLoops,
28+
minVisits,
29+
}) => {
2930
const [dotString, setDotString] = useState<string | null>(null);
3031
const [filteredDotString, setFilteredDotString] = useState<string | null>(null);
3132
const [topDotString, setTopDotString] = useState<string | null>(null);
32-
const {selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences} = useContext(Context);
33+
const { selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences } = useContext(Context);
3334

3435
// Refs for rendering the Graphviz graphs
3536
const graphRefMain = useRef<HTMLDivElement>(null);
@@ -193,36 +194,21 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
193194
<div className="graph-item flex flex-col items-center">
194195
<h2 className="text-lg font-semibold text-center mb-2">Selected Sequence</h2>
195196
<div ref={graphRefTop} className="w-auto h-auto"></div>
196-
<button
197-
className="px-6 py-3 bg-blue-600 text-white rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 transition duration-300"
198-
onClick={() => exportGraphAsPNG(graphRefTop, 'selected_sequence')}
199-
>
200-
Export as PNG
201-
</button>
197+
<ExportButton onClick={() => exportGraphAsPNG(graphRefTop, 'selected_sequence')} />
202198
</div>
203199
)}
204200
{dotString && (
205201
<div className="graph-item flex flex-col items-center">
206202
<h2 className="text-lg font-semibold text-center mb-2">All Students, All Paths</h2>
207203
<div ref={graphRefMain} className="w-auto h-auto"></div>
208-
<button
209-
className="px-6 py-3 bg-blue-600 text-white rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 transition duration-300"
210-
onClick={() => exportGraphAsPNG(graphRefMain, 'all_students')}
211-
>
212-
Export as PNG
213-
</button>
204+
<ExportButton onClick={() => exportGraphAsPNG(graphRefMain, 'all_students')} />
214205
</div>
215206
)}
216207
{filteredDotString && (
217208
<div className="graph-item flex flex-col items-center">
218209
<h2 className="text-lg font-semibold text-center mb-4">Filtered Graph</h2>
219210
<div ref={graphRefFiltered} className="w-auto h-auto"></div>
220-
<button
221-
className="px-6 py-3 bg-blue-600 text-white rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75 transition duration-300"
222-
onClick={() => exportGraphAsPNG(graphRefFiltered, 'filtered_graph')}
223-
>
224-
Export as PNG
225-
</button>
211+
<ExportButton onClick={() => exportGraphAsPNG(graphRefFiltered, 'filtered_graph')} />
226212
</div>
227213
)}
228214
</div>
@@ -232,3 +218,20 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
232218
}
233219

234220
export default GraphvizParent;
221+
222+
223+
interface ExportButtonProps {
224+
onClick: () => void;
225+
label?: string;
226+
}
227+
228+
function ExportButton({ onClick, label = "Export Image" }: ExportButtonProps) {
229+
return (
230+
<Button
231+
variant={'secondary'}
232+
onClick={onClick}
233+
>
234+
{label}
235+
</Button>
236+
);
237+
};

src/components/SequenceSelector.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,24 @@ const SequenceSelector: React.FC<SequenceSelectorProps> = ({
1616
}) => {
1717
// Display a message when no sequences are present
1818
if (sequences == null || sequences.length === 0) {
19-
return <div>No sequences available</div>;
19+
return <div className="text-sm">No sequences available</div>;
2020
}
2121

2222
return (
23-
<div>
23+
<div className="space-y-2">
2424
<select
25-
value={selectedSequence?.join(',') || ' '} // Set the selected value from the state or an empty string
26-
onChange={(e) => onSequenceSelect(e.target.value.split(','))} // Handle sequence selection
25+
className="w-full px-3 py-2 text-sm bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
26+
value={selectedSequence?.join(',') || ' '}
27+
onChange={(e) => onSequenceSelect(e.target.value.split(','))}
2728
>
29+
<option value=" " disabled>Select a sequence...</option>
2830
{sequences.map((seq: SequenceCount) => (
29-
<option key={seq.sequence!.join(',')} value={seq.sequence!.join(',')}>
30-
(Color nodes by path taken {seq.count} times)
31+
<option
32+
key={seq.sequence!.join(',')}
33+
value={seq.sequence!.join(',')}
34+
className="py-1"
35+
>
36+
Path taken {seq.count} times
3137
</option>
3238
))}
3339
</select>

src/components/selfLoopSwitch.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,30 @@ interface SwitchProps {
88

99
const Switch: React.FC<SwitchProps> = ({ isOn, handleToggle }) => {
1010
return (
11-
<div className="switch-container" onClick={handleToggle}>
12-
<label>Self Loops?</label>
13-
<div className={`switch ${isOn ? true:false}`}>
14-
<div className="switch-handle"></div>
15-
</div>
11+
<div className="flex items-center justify-between space-x-4">
12+
<label className="text-sm font-medium text-gray-700">
13+
Include Self Loops
14+
</label>
15+
<button
16+
type="button"
17+
role="switch"
18+
aria-checked={isOn}
19+
onClick={handleToggle}
20+
className={`
21+
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
22+
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
23+
${isOn ? 'bg-blue-600' : 'bg-gray-200'}
24+
`}
25+
>
26+
<span className="sr-only">Toggle self loops</span>
27+
<span
28+
className={`
29+
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0
30+
transition duration-200 ease-in-out
31+
${isOn ? 'translate-x-5' : 'translate-x-0'}
32+
`}
33+
/>
34+
</button>
1635
</div>
1736
);
1837
};

0 commit comments

Comments
 (0)