Skip to content

Commit

Permalink
Update dependencies and add new UI components
Browse files Browse the repository at this point in the history
  • Loading branch information
emoltz-carnegie committed Dec 19, 2024
1 parent 01b1b3f commit 45cd05a
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 101 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
},
"dependencies": {
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
Expand Down
98 changes: 55 additions & 43 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import './App.css';
import { useContext, useEffect, useMemo, useState } from 'react';
import DropZone from './components/DropZone';
import { useContext, useMemo, useState } from 'react';
import { Button } from './components/ui/button';
import Upload from "@/components/Upload.tsx";
import GraphvizParent from "@/components/GraphvizParent.tsx";
import FilterComponent from './components/FilterComponent.tsx';
import SelfLoopSwitch from './components/selfLoopSwitch.tsx';
import Slider from './components/slider.tsx';
import Slider from '@/components/slider.tsx';
import SequenceSelector from "@/components/SequenceSelector.tsx";
import { Context, SequenceCount } from "@/Context.tsx";
import { Separator } from "@/components/ui/separator"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"

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

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

const toggleMenu = () => setMenuVisible(!menuVisible);
/**
* Toggles the self-loops inclusion in the graph by switching the state.
*/
Expand All @@ -71,7 +73,7 @@ function App() {
*
* @param {boolean} loading - Whether the data is currently loading/processing.
*/

// Rendering the components that allow user interaction and display the graph
return (
<div className='p-3'>
Expand All @@ -88,8 +90,8 @@ function App() {
</div>
</div>
</header>
{!showControls && <Upload onDataProcessed={handleDataProcessed} />}
{!showControls && <Upload onDataProcessed={handleDataProcessed} />}

{loading && <Loading />}
{/* Display Error Message */}
{error && (
Expand All @@ -103,8 +105,8 @@ function App() {

{
showControls && (
<div className="p-5 m-2">
<div className="p-5 m-2 flex flex-col gap-3">

<div className="selected-sequence-bar flex justify-between bg-gray-200 p-4 mb-4">
<h2 className="text-lg font-semibold">Selected Sequence:</h2>
{selectedSequence && (
Expand All @@ -114,37 +116,47 @@ function App() {
)}
</div>
{/* Properties Button */}
<button
className="flex-auto top-10 left-10 bg-blue-500 text-white z-30 rounded-md p-4 mb-4"
onClick={toggleMenu}
>
{menuVisible ? "Hide Properties" : "Show Properties"}
</button>

{/* Properties Menu */}

{menuVisible && (
<div className="absolute top-50 left-4 p-4 bg-gray-200 z-30 shadow-lg w-85 rounded-lg">
<h3 className="text-md font-semibold mb-4">Properties Menu</h3>
<FilterComponent onFilterChange={setFilter} />
{/*{selectedSequence && (*/}
{/* <h2>{selectedSequence.toString().split('->').join(' -> ')}</h2>*/}
{/*)}*/}
<SequenceSelector
onSequenceSelect={handleSelectSequence}
sequences={top5Sequences!}
selectedSequence={selectedSequence}
/>
<SelfLoopSwitch isOn={selfLoops} handleToggle={handleToggle} />
<Slider
step={5}
min={0}
max={5000}
value={minVisits}
onChange={handleSlider}
/>
</div>
)}
<Popover>
<PopoverTrigger className="w-fit bg-slate-500 p-3 rounded-lg text-white">Properties</PopoverTrigger>
<PopoverContent className="w-96 bg-white rounded-lg shadow-lg p-6 border border-gray-200 mx-10">
<div className="flex flex-col space-y-6">
{/* Filter Section */}
<div className="space-y-2">
<h3 className="text-lg font-semibold text-gray-900">Filters</h3>
<FilterComponent onFilterChange={setFilter} />
</div>

{/* Sequence Section */}
<div className="space-y-2">
<h3 className="text-lg font-semibold text-gray-900">Sequences</h3>
<SequenceSelector
onSequenceSelect={handleSelectSequence}
sequences={top5Sequences || []}
selectedSequence={selectedSequence}
/>
</div>

{/* Controls Section */}
<div className="space-y-4">
<div className="pb-2 border-b border-gray-200">
<SelfLoopSwitch isOn={selfLoops} handleToggle={handleToggle} />
</div>

<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Edge Visits</label>
<Slider
step={5}
min={0}
max={5000}
value={minVisits}
onChange={handleSlider}
/>
</div>
</div>
</div>
</PopoverContent>
</Popover>


{/* Graph and Data Display */}
{!loading && csvData && (
Expand Down
37 changes: 23 additions & 14 deletions src/components/FilterComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import React from 'react';

import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
interface FilterComponentProps {
onFilterChange: (filter: string) => void;
}

const FilterComponent: React.FC<FilterComponentProps> = ({ onFilterChange }) => {
const handleFilterChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
onFilterChange(event.target.value);
};

return (
<div>
<label htmlFor="statusFilter">Filter by Completion Status:</label>
<select id="statusFilter" onChange={handleFilterChange}>
<option value="">All</option>
<option value="GRADUATED">Graduated</option>
<option value="PROMOTED">Promoted</option>
</select>
</div>
<div className="space-y-2">
<Select onValueChange={(value) => onFilterChange(value)}>
<SelectTrigger>
<SelectValue placeholder="Filter by Status" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="ALL STATUSES">All Statuses</SelectItem>
<SelectItem value="GRADUATED">Graduated</SelectItem>
<SelectItem value="PROMOTED">Promoted</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
);
};

Expand Down
57 changes: 30 additions & 27 deletions src/components/GraphvizParent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// React component code
import React, {useContext, useEffect, useRef, useState} from 'react';
import {graphviz} from 'd3-graphviz';
import React, { useContext, useEffect, useRef, useState } from 'react';
import { graphviz } from 'd3-graphviz';
import {
generateDotString,
normalizeThicknesses,
Expand All @@ -11,7 +11,8 @@ import {
} from './GraphvizProcessing';
import ErrorBoundary from "@/components/errorBoundary.tsx";
import '../GraphvizContainer.css';
import {Context} from "@/Context.tsx";
import { Context } from "@/Context.tsx";
import { Button } from './ui/button';

interface GraphvizParentProps {
csvData: string;
Expand All @@ -21,15 +22,15 @@ interface GraphvizParentProps {
}

const GraphvizParent: React.FC<GraphvizParentProps> = ({
csvData,
filter,
selfLoops,
minVisits,
}) => {
csvData,
filter,
selfLoops,
minVisits,
}) => {
const [dotString, setDotString] = useState<string | null>(null);
const [filteredDotString, setFilteredDotString] = useState<string | null>(null);
const [topDotString, setTopDotString] = useState<string | null>(null);
const {selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences} = useContext(Context);
const { selectedSequence, setSelectedSequence, top5Sequences, setTop5Sequences } = useContext(Context);

// Refs for rendering the Graphviz graphs
const graphRefMain = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -193,36 +194,21 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
<div className="graph-item flex flex-col items-center">
<h2 className="text-lg font-semibold text-center mb-2">Selected Sequence</h2>
<div ref={graphRefTop} className="w-auto h-auto"></div>
<button
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"
onClick={() => exportGraphAsPNG(graphRefTop, 'selected_sequence')}
>
Export as PNG
</button>
<ExportButton onClick={() => exportGraphAsPNG(graphRefTop, 'selected_sequence')} />
</div>
)}
{dotString && (
<div className="graph-item flex flex-col items-center">
<h2 className="text-lg font-semibold text-center mb-2">All Students, All Paths</h2>
<div ref={graphRefMain} className="w-auto h-auto"></div>
<button
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"
onClick={() => exportGraphAsPNG(graphRefMain, 'all_students')}
>
Export as PNG
</button>
<ExportButton onClick={() => exportGraphAsPNG(graphRefMain, 'all_students')} />
</div>
)}
{filteredDotString && (
<div className="graph-item flex flex-col items-center">
<h2 className="text-lg font-semibold text-center mb-4">Filtered Graph</h2>
<div ref={graphRefFiltered} className="w-auto h-auto"></div>
<button
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"
onClick={() => exportGraphAsPNG(graphRefFiltered, 'filtered_graph')}
>
Export as PNG
</button>
<ExportButton onClick={() => exportGraphAsPNG(graphRefFiltered, 'filtered_graph')} />
</div>
)}
</div>
Expand All @@ -232,3 +218,20 @@ const GraphvizParent: React.FC<GraphvizParentProps> = ({
}

export default GraphvizParent;


interface ExportButtonProps {
onClick: () => void;
label?: string;
}

function ExportButton({ onClick, label = "Export Image" }: ExportButtonProps) {
return (
<Button
variant={'secondary'}
onClick={onClick}
>
{label}
</Button>
);
};
18 changes: 12 additions & 6 deletions src/components/SequenceSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@ const SequenceSelector: React.FC<SequenceSelectorProps> = ({
}) => {
// Display a message when no sequences are present
if (sequences == null || sequences.length === 0) {
return <div>No sequences available</div>;
return <div className="text-sm">No sequences available</div>;
}

return (
<div>
<div className="space-y-2">
<select
value={selectedSequence?.join(',') || ' '} // Set the selected value from the state or an empty string
onChange={(e) => onSequenceSelect(e.target.value.split(','))} // Handle sequence selection
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"
value={selectedSequence?.join(',') || ' '}
onChange={(e) => onSequenceSelect(e.target.value.split(','))}
>
<option value=" " disabled>Select a sequence...</option>
{sequences.map((seq: SequenceCount) => (
<option key={seq.sequence!.join(',')} value={seq.sequence!.join(',')}>
(Color nodes by path taken {seq.count} times)
<option
key={seq.sequence!.join(',')}
value={seq.sequence!.join(',')}
className="py-1"
>
Path taken {seq.count} times
</option>
))}
</select>
Expand Down
29 changes: 24 additions & 5 deletions src/components/selfLoopSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,30 @@ interface SwitchProps {

const Switch: React.FC<SwitchProps> = ({ isOn, handleToggle }) => {
return (
<div className="switch-container" onClick={handleToggle}>
<label>Self Loops?</label>
<div className={`switch ${isOn ? true:false}`}>
<div className="switch-handle"></div>
</div>
<div className="flex items-center justify-between space-x-4">
<label className="text-sm font-medium text-gray-700">
Include Self Loops
</label>
<button
type="button"
role="switch"
aria-checked={isOn}
onClick={handleToggle}
className={`
relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent
transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
${isOn ? 'bg-blue-600' : 'bg-gray-200'}
`}
>
<span className="sr-only">Toggle self loops</span>
<span
className={`
pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0
transition duration-200 ease-in-out
${isOn ? 'translate-x-5' : 'translate-x-0'}
`}
/>
</button>
</div>
);
};
Expand Down
Loading

0 comments on commit 45cd05a

Please sign in to comment.