Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions app/graph/[algorithm]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AlgorithmProvider } from "@/context/AlgorithmContext";
import { Metadata } from "next";
import { availableAlgorithms } from "@/lib/algorithms/metadata";
import { constructMetadata } from "@/lib/seo/metadata";

type Props = {
params: Promise<{ algorithm: string }>;
};

export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
const { algorithm } = params;

// Find the algorithm in our available list
const algorithmInfo = availableAlgorithms[algorithm];

if (!algorithmInfo) {
return constructMetadata({
title: "Algorithm Not Found",
description: "The requested algorithm could not be found.",
path: `/graph/${algorithm}`,
});
}

const { name, description, difficulty, category } = algorithmInfo;

return constructMetadata({
title: name,
description,
path: `/graph/${algorithm}`,
keywords: [
algorithm,
name.toLowerCase(),
`${name.toLowerCase()} visualization`,
`${name.toLowerCase()} explanation`,
`${name.toLowerCase()} algorithm`,
`${difficulty} algorithm`,
`${category} algorithm`,
],
});
}

export default function GraphLayout({
children,
}: {
children: React.ReactNode;
}) {
return <AlgorithmProvider>{children}</AlgorithmProvider>;
}
79 changes: 79 additions & 0 deletions app/graph/[algorithm]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";

import React, { useEffect } from "react";
import { useParams } from "next/navigation";
import PageLayout from "@/components/layout/PageLayout";
import AlgorithmVisualizer from "@/components/visualizer/AlgorithmVisualizer";
import { useAlgorithm } from "@/context/AlgorithmContext";
import { getAlgorithmByName } from "@/lib/algorithms";
import { availableAlgorithms } from "@/lib/algorithms/metadata";

export default function GraphPage() {
const params = useParams();
const algorithmKey = params.algorithm as string;
const { dispatch, state } = useAlgorithm();

const algorithmInfo = availableAlgorithms[algorithmKey];

// Set the current algorithm and generate visualization
useEffect(() => {
if (algorithmKey) {
dispatch({ type: "SET_ALGORITHM", payload: algorithmKey });

// Generate visualization if not already generated OR if algorithm changed
if (!state.visualizationData || algorithmKey !== state.algorithm) {
const algorithmFunction = getAlgorithmByName(algorithmKey);
if (algorithmFunction) {
try {
// For graph algorithms, ensure we're using a valid start vertex
// Our graph only has vertices 0-5, so ensure the start vertex is in this range
// If there's no valid target set, use vertex 0 as default
const startVertex =
state.target === undefined
? 0
: state.target >= 0 && state.target < 6
? state.target
: 0;
// Set the target so it's properly displayed in the UI
dispatch({ type: "SET_TARGET", payload: startVertex });
const viz = algorithmFunction([], startVertex);
dispatch({ type: "GENERATE_VISUALIZATION", payload: viz });
} catch (error) {
console.error("Error generating visualization:", error);
}
}
}
}
}, [
algorithmKey,
dispatch,
state.algorithm,
state.data,
state.visualizationData,
state.target,
]);

if (!algorithmInfo) {
return (
<PageLayout title="Algorithm Not Found">
<div className="text-center py-12">
<h2 className="heading-lg text-red-600">Algorithm Not Found</h2>
<p className="mt-4 text-gray-600">
The algorithm you are looking for does not exist or is not
available.
</p>
</div>
</PageLayout>
);
}

return (
<PageLayout
title={algorithmInfo.name}
subtitle={algorithmInfo.subtitle}
algorithmData={state.visualizationData || undefined}
>
<AlgorithmVisualizer />
</PageLayout>
);
}
22 changes: 22 additions & 0 deletions app/graph/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import PageLayout from "@/components/layout/PageLayout";
import AlgorithmCard from "@/components/AlgorithmCard";
import { availableAlgorithms } from "@/lib/algorithms/metadata";

export default function GraphAlgorithms() {
const graphAlgorithms = Object.entries(availableAlgorithms).filter(
([, algo]) => algo.category === "graph"
);

return (
<PageLayout
title="Graph Algorithms"
subtitle="Graph algorithms are procedures to search, detect cyles, or understand network cycles in node and vertex graphs."
>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{graphAlgorithms.map((algorithm) => (
<AlgorithmCard key={algorithm[0]} algorithm={algorithm[1]} />
))}
</div>
</PageLayout>
);
}
67 changes: 49 additions & 18 deletions components/visualizer/AlgorithmVisualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,38 @@

import SortingVisualization from "./SortingVisualization";
import SearchVisualization from "./SearchVisualization";
import GraphVisualization from "./GraphVisualization";
import VisualizerControls from "./VisualizerControls";
import AlgorithmInfo from "./AlgorithmInfo";
import AlgorithmPseudocode from "./AlgorithmPseudocode";
import ColorLegend from "./ColorLegend";
import { useAlgorithm } from "@/context/AlgorithmContext";
import { getAlgorithmByName } from "@/lib/algorithms";
import { SearchStep, SortingStep } from "@/lib/types";
import { GraphStep, SearchStep, SortingStep } from "@/lib/types";

export default function AlgorithmVisualizer() {
const { state, dispatch } = useAlgorithm();
const { currentStep, algorithm, data, visualizationData, target } = state;

// Generate a new random array
const handleGenerateNewArray = () => {
// Function to generate new data based on algorithm type
const handleGenerateNewData = () => {
// For graph algorithms, we just need to regenerate with a new starting vertex
if (category === "graph") {
const algorithmFunction = getAlgorithmByName(algorithm);
if (algorithmFunction) {
try {
// Generate a random starting vertex between 0-5
const startVertex = Math.floor(Math.random() * 6);
const viz = algorithmFunction([], startVertex);
dispatch({ type: "GENERATE_VISUALIZATION", payload: viz });
} catch (error) {
console.error("Error generating graph visualization:", error);
}
}
return;
}

// For array-based algorithms, generate a new random array
dispatch({
type: "GENERATE_RANDOM_DATA",
payload: { min: 5, max: 95, length: 15 },
Expand All @@ -25,24 +43,27 @@ export default function AlgorithmVisualizer() {
const algorithmFunction = getAlgorithmByName(algorithm);
if (algorithmFunction) {
try {
const newData = [...state.data];
let viz;

// Special handling for binary search - ensure sorted array and target exists
if (algorithm === "binarySearch") {
newData.sort((a, b) => a - b);
// For binary search - ensure sorted array and target exists
const newData = [...state.data].sort((a, b) => a - b);
viz = algorithmFunction(newData, target);
} else {
// For other algorithms like sorting
viz = algorithmFunction(state.data, target);
}

const viz = algorithmFunction(newData, target);
dispatch({ type: "GENERATE_VISUALIZATION", payload: viz });
} catch (error) {
console.error("Error generating visualization:", error);
}
}
};

// Check if the current algorithm is a search algorithm
const isSearchAlgorithm = () => {
return visualizationData?.category === "searching";
// Check the algorithm category
const getAlgorithmCategory = () => {
return visualizationData?.category || "";
};

// Find the maximum value in the array for scaling
Expand All @@ -59,7 +80,7 @@ export default function AlgorithmVisualizer() {
);
}

const isSearching = isSearchAlgorithm();
const category = getAlgorithmCategory();

return (
<div className="space-y-8">
Expand All @@ -68,26 +89,36 @@ export default function AlgorithmVisualizer() {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<div className="card">
{isSearching ? (
{category === "sorting" && (
<SortingVisualization
step={visualizationData.steps[currentStep] as SortingStep}
maxValue={maxValue}
/>
)}
{category === "searching" && (
<SearchVisualization
step={visualizationData.steps[currentStep] as SearchStep}
maxValue={maxValue}
/>
) : (
<SortingVisualization
step={visualizationData.steps[currentStep] as SortingStep}
maxValue={maxValue}
)}
{category === "graph" && (
<GraphVisualization
step={visualizationData.steps[currentStep] as GraphStep}
/>
)}
</div>

<VisualizerControls
currentStep={currentStep}
totalSteps={visualizationData.steps.length}
onGenerateNewArray={handleGenerateNewArray}
onGenerateNewArray={handleGenerateNewData}
algorithmCategory={category}
/>

<ColorLegend isSearchAlgorithm={isSearching} />
<ColorLegend
isSearchAlgorithm={category === "searching"}
isGraphAlgorithm={category === "graph"}
/>
</div>

<div className="lg:col-span-1">
Expand Down
19 changes: 16 additions & 3 deletions components/visualizer/ColorLegend.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
interface ColorLegendProps {
isSearchAlgorithm?: boolean;
isGraphAlgorithm?: boolean;
}

export default function ColorLegend({
isSearchAlgorithm = false,
isGraphAlgorithm = false,
}: ColorLegendProps) {
const sortingLegendItems = [
{ color: "bg-blue-400", label: "Unsorted" },
Expand All @@ -19,9 +21,20 @@ export default function ColorLegend({
{ color: "bg-green-500", label: "Found" },
];

const legendItems = isSearchAlgorithm
? searchLegendItems
: sortingLegendItems;
const graphLegendItems = [
{ color: "bg-blue-300", label: "Unvisited Vertex" },
{ color: "bg-yellow-300", label: "Current Vertex" },
{ color: "bg-green-300", label: "Visited Vertex" },
{ color: "bg-red-400", label: "Path Edge" },
];

let legendItems = sortingLegendItems;

if (isSearchAlgorithm) {
legendItems = searchLegendItems;
} else if (isGraphAlgorithm) {
legendItems = graphLegendItems;
}

return (
<div className="card p-4">
Expand Down
Loading
Loading