TouseefAhmad's picture
Update app.py
129c144 verified
Raw
History Blame Contribute Delete
26.9 kB
import gradio as gr
import matplotlib.pyplot as plt
import numpy as np
from io import BytesIO
import networkx as nx
from collections import deque
import traceback
class DSAVisualizer:
def __init__(self):
self.reset_state()
def reset_state(self):
self.current_data = []
self.steps = []
self.current_step = 0
self.pseudocode = []
self.active_lines = []
self.graph_dict = {}
self.error_message = ""
def generate_data(self, data_size=10, data_type="Random"):
try:
if data_size < 5:
data_size = 5
elif data_size > 50:
data_size = 50
if data_type == "Random":
self.current_data = np.random.randint(1, 100, data_size)
elif data_type == "Ascending":
self.current_data = np.arange(1, data_size + 1)
elif data_type == "Descending":
self.current_data = np.arange(data_size, 0, -1)
elif data_type == "Nearly Sorted":
self.current_data = np.arange(1, data_size + 1)
for _ in range(max(1, data_size // 10)):
i, j = np.random.randint(0, data_size, 2)
self.current_data[i], self.current_data[j] = self.current_data[j], self.current_data[i]
return list(self.current_data)
except Exception as e:
self.error_message = f"Data generation error: {str(e)}"
return []
def bubble_sort(self, arr):
try:
arr = [int(x) for x in arr] # Ensure integers
n = len(arr)
steps = [arr.copy()]
pseudocode = [
"procedure bubbleSort(A : list)",
" n = length(A)",
" repeat",
" swapped = false",
" for i from 1 to n-1:",
" if A[i-1] > A[i]:",
" swap(A[i-1], A[i])",
" swapped = true",
" until not swapped"
]
active_line = [0]
swapped = True
while swapped:
swapped = False
for j in range(1, n):
active_line.append(5)
if steps[-1][j-1] > steps[-1][j]:
active_line.append(6)
new_step = steps[-1].copy()
new_step[j-1], new_step[j] = new_step[j], new_step[j-1]
steps.append(new_step)
swapped = True
active_line.append(7)
active_line.append(4)
active_line.append(3)
return steps, pseudocode, active_line
except Exception as e:
self.error_message = f"Bubble sort error: {str(e)}"
return [], [], []
def insertion_sort(self, arr):
try:
arr = [int(x) for x in arr] # Ensure integers
steps = [arr.copy()]
pseudocode = [
"procedure insertionSort(A : list)",
" for j from 1 to length(A)-1:",
" key = A[j]",
" i = j-1",
" while i >= 0 and A[i] > key:",
" A[i+1] = A[i]",
" i = i-1",
" A[i+1] = key"
]
active_line = [0]
arr = arr.copy()
n = len(arr)
for j in range(1, n):
active_line.append(1)
key = arr[j]
active_line.append(2)
i = j-1
active_line.append(3)
while i >= 0 and arr[i] > key:
active_line.append(5)
arr[i+1] = arr[i]
steps.append(arr.copy())
active_line.append(6)
i = i-1
active_line.append(3)
active_line.append(7)
arr[i+1] = key
steps.append(arr.copy())
return steps, pseudocode, active_line
except Exception as e:
self.error_message = f"Insertion sort error: {str(e)}"
return [], [], []
def dfs(self, graph_dict, start):
try:
graph = self.parse_graph(graph_dict)
if not graph:
return [], [], []
visited = set()
stack = [start]
steps = []
pseudocode = [
"procedure DFS(G, start):",
" visited = set()",
" stack = [start]",
" while stack not empty:",
" vertex = stack.pop()",
" if vertex not in visited:",
" visited.add(vertex)",
" for neighbor in G[vertex]:",
" if neighbor not in visited:",
" stack.push(neighbor)"
]
active_line = [0]
steps.append({"visited": set(), "current": None, "stack": stack.copy(), "graph": graph})
active_line.append(1)
while stack:
active_line.append(3)
vertex = stack.pop()
active_line.append(4)
if vertex not in visited:
active_line.append(5)
visited.add(vertex)
active_line.append(6)
for neighbor in graph.get(vertex, []):
active_line.append(7)
if neighbor not in visited:
stack.append(neighbor)
steps.append({"visited": visited.copy(),
"current": vertex,
"stack": stack.copy(),
"graph": graph})
active_line.append(3)
return steps, pseudocode, active_line
except Exception as e:
self.error_message = f"DFS error: {str(e)}"
return [], [], []
def bfs(self, graph_dict, start):
try:
graph = self.parse_graph(graph_dict)
if not graph:
return [], [], []
visited = set()
queue = deque([start])
steps = []
pseudocode = [
"procedure BFS(G, start):",
" visited = set()",
" queue = deque([start])",
" while queue not empty:",
" vertex = queue.popleft()",
" if vertex not in visited:",
" visited.add(vertex)",
" for neighbor in G[vertex]:",
" if neighbor not in visited:",
" queue.append(neighbor)"
]
active_line = [0]
steps.append({"visited": set(), "current": None, "queue": list(queue), "graph": graph})
active_line.append(1)
while queue:
active_line.append(3)
vertex = queue.popleft()
active_line.append(4)
if vertex not in visited:
active_line.append(5)
visited.add(vertex)
active_line.append(6)
for neighbor in graph.get(vertex, []):
active_line.append(7)
if neighbor not in visited:
queue.append(neighbor)
steps.append({"visited": visited.copy(),
"current": vertex,
"queue": list(queue),
"graph": graph})
active_line.append(3)
return steps, pseudocode, active_line
except Exception as e:
self.error_message = f"BFS error: {str(e)}"
return [], [], []
def visualize_sorting(self, algorithm, data):
try:
# Convert data to list of integers
if isinstance(data, str):
data = [int(np.int64(x.strip())) for x in data.strip('[]').split(',') if x.strip()]
elif not isinstance(data, list):
data = list(data)
if algorithm == "Bubble Sort":
self.steps, self.pseudocode, self.active_lines = self.bubble_sort(data)
elif algorithm == "Insertion Sort":
self.steps, self.pseudocode, self.active_lines = self.insertion_sort(data)
else:
self.error_message = f"Algorithm '{algorithm}' not implemented yet"
return None, self.create_pseudocode(0), self.error_message
self.current_step = 0
return self.create_plot(), self.create_pseudocode(0), ""
except Exception as e:
self.error_message = f"Visualization error: {str(e)}"
return None, "", self.error_message
def visualize_graph(self, algorithm, graph_input, start):
try:
self.steps, self.pseudocode, self.active_lines = (
self.dfs(graph_input, start) if algorithm == "DFS"
else self.bfs(graph_input, start)
)
self.current_step = 0
return self.create_graph_plot(), self.create_pseudocode(0), ""
except Exception as e:
self.error_message = f"Graph visualization error: {str(e)}"
return None, "", self.error_message
def create_plot(self):
try:
fig, ax = plt.subplots(figsize=(10, 6))
if self.steps and self.current_step < len(self.steps):
current_data = self.steps[self.current_step]
colors = ['#1f77b4' for _ in current_data]
# Highlight recently swapped elements
if self.current_step > 0 and self.current_step < len(self.steps):
prev = self.steps[self.current_step-1]
for i in range(len(current_data)):
if i < len(prev) and current_data[i] != prev[i]:
colors[i] = '#ff7f0e'
ax.bar(range(len(current_data)), current_data, color=colors)
ax.set_title(f'Step {self.current_step}/{len(self.steps)-1}')
ax.set_xlabel('Index')
ax.set_ylabel('Value')
ax.grid(axis='y', linestyle='--', alpha=0.7)
else:
ax.text(0.5, 0.5, "No data to visualize",
ha='center', va='center', fontsize=16)
buf = BytesIO()
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
plt.close(fig)
return buf.getvalue()
except Exception as e:
self.error_message = f"Plot creation error: {str(e)}"
return None
def create_graph_plot(self):
try:
if not self.steps or self.current_step >= len(self.steps):
fig, ax = plt.subplots(figsize=(10, 8))
ax.text(0.5, 0.5, "No graph data to visualize",
ha='center', va='center', fontsize=16)
buf = BytesIO()
plt.savefig(buf, format='png')
plt.close(fig)
return buf.getvalue()
state = self.steps[self.current_step]
graph_dict = state["graph"]
G = nx.Graph()
for node, neighbors in graph_dict.items():
for neighbor in neighbors:
if neighbor in graph_dict: # Ensure neighbor exists
G.add_edge(node, neighbor)
# Add isolated nodes
for node in graph_dict:
if node not in G:
G.add_node(node)
pos = nx.spring_layout(G, seed=42)
fig, ax = plt.subplots(figsize=(10, 8))
node_colors = []
for node in G.nodes():
if node == state.get('current'):
node_colors.append('#d62728') # Current node
elif node in state.get('visited', set()):
node_colors.append('#2ca02c') # Visited nodes
elif node in state.get('stack', []) or node in state.get('queue', []):
node_colors.append('#ff7f0e') # Nodes in stack/queue
else:
node_colors.append('#1f77b4') # Unexplored nodes
nx.draw_networkx_nodes(G, pos, node_size=800,
node_color=node_colors, alpha=0.9, ax=ax)
nx.draw_networkx_edges(G, pos, width=1.5, alpha=0.5, ax=ax)
nx.draw_networkx_labels(G, pos, font_size=12,
font_weight='bold', font_color='white', ax=ax)
algorithm = "DFS" if 'stack' in state else "BFS"
title = f"{algorithm} Traversal - Step {self.current_step}/{len(self.steps)-1}\n"
title += f"Current: {state.get('current', 'None')} | "
if 'stack' in state:
title += f"Stack: {state['stack']}"
elif 'queue' in state:
title += f"Queue: {state['queue']}"
ax.set_title(title, fontsize=14)
ax.set_axis_off()
buf = BytesIO()
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
plt.close(fig)
return buf.getvalue()
except Exception as e:
self.error_message = f"Graph plot error: {str(e)}"
return None
def create_pseudocode(self, step):
try:
if not self.pseudocode or not self.active_lines:
return ""
# Find the active line for this step
if step < len(self.active_lines):
active_line = self.active_lines[step]
else:
active_line = self.active_lines[-1] if self.active_lines else 0
html = "<div style='font-family: monospace; background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 8px; line-height: 1.5;'>"
for i, line in enumerate(self.pseudocode):
if i == active_line:
html += f"<div style='background: #44475a; padding: 8px; border-left: 3px solid #bd93f9;'><b>{line}</b></div>"
else:
html += f"<div style='padding: 8px;'>{line}</div>"
html += "</div>"
return html
except Exception as e:
self.error_message = f"Pseudocode error: {str(e)}"
return ""
def next_step(self):
if self.current_step < len(self.steps) - 1:
self.current_step += 1
return self.update_display()
def prev_step(self):
if self.current_step > 0:
self.current_step -= 1
return self.update_display()
def update_display(self):
try:
if not self.steps:
return None, "", "No steps available"
if isinstance(self.steps[0], list): # Sorting visualization
plot = self.create_plot()
else: # Graph visualization
plot = self.create_graph_plot()
return plot, self.create_pseudocode(self.current_step), ""
except Exception as e:
return None, "", f"Update error: {str(e)}"
def parse_graph(self, graph_str):
try:
graph = {}
for line in graph_str.strip().split('\n'):
if ':' in line:
node, neighbors = line.split(':', 1)
node = node.strip()
if node not in graph:
graph[node] = []
neighbors = [n.strip() for n in neighbors.split(',') if n.strip()]
graph[node].extend(neighbors)
# Add neighbors that might not be defined yet
for neighbor in neighbors:
if neighbor not in graph:
graph[neighbor] = []
return graph
except Exception as e:
self.error_message = f"Graph parse error: {str(e)}"
return {}
# Initialize visualizer
visualizer = DSAVisualizer()
# CSS for styling
custom_css = """
:root {
--primary: #6e40c9;
--secondary: #3d5afe;
--accent: #ff4081;
--dark: #2d2d2d;
--light: #f8f8f8;
--success: #4CAF50;
--error: #f44336;
}
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: var(--light);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
h1, h2, h3 {
background: linear-gradient(90deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.tab {
background: rgba(45, 45, 45, 0.8) !important;
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
border: 1px solid rgba(255,255,255,0.1);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary), var(--secondary)) !important;
border: none !important;
border-radius: 8px !important;
font-weight: 600 !important;
text-transform: uppercase !important;
letter-spacing: 0.5px !important;
transition: all 0.3s ease !important;
color: white !important;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(110, 64, 201, 0.3) !important;
}
.control-panel {
background: rgba(45, 45, 45, 0.6) !important;
border-radius: 12px;
padding: 20px;
border: 1px solid rgba(255,255,255,0.1);
}
.visualization-container {
background: rgba(45, 45, 45, 0.6);
border-radius: 12px;
padding: 20px;
border: 1px solid rgba(255,255,255,0.1);
min-height: 500px;
}
.error-box {
background: var(--error) !important;
color: white !important;
padding: 10px;
border-radius: 8px;
margin-top: 10px;
font-weight: bold;
}
.success-box {
background: var(--success) !important;
color: white !important;
padding: 10px;
border-radius: 8px;
margin-top: 10px;
font-weight: bold;
}
footer {
text-align: center;
margin-top: 20px;
color: rgba(255,255,255,0.6);
font-size: 0.9em;
}
"""
# Gradio Interface
with gr.Blocks(theme=gr.themes.Soft(primary_hue="purple"),
css=custom_css,
title="AlgoViz Pro") as demo:
# Header
with gr.Row():
gr.Markdown("""
<div style="text-align: center; width: 100%;">
<h1 style="font-size: 2.5rem; margin-bottom: 0.5rem;">🌟 AlgoViz Pro</h1>
<p style="font-size: 1.2rem; color: #a0a0c0; max-width: 800px; margin: 0 auto;">
Interactive Data Structures & Algorithms Visualization Platform
</p>
</div>
""")
# Error display
error_output = gr.HTML(visible=False, elem_classes=["error-box"])
success_output = gr.HTML(visible=False, elem_classes=["success-box"])
# Main Content
with gr.Tab("🧮 Sorting Algorithms"):
with gr.Row():
# Control Panel
with gr.Column(scale=1, elem_classes="control-panel"):
data_type = gr.Radio(
choices=["Random", "Ascending", "Descending", "Nearly Sorted"],
value="Random", label="Data Distribution"
)
data_size = gr.Slider(5, 50, value=10, step=1, label="Data Size")
gen_btn = gr.Button("♻️ Generate Data", variant="primary")
algorithm = gr.Dropdown(
["Bubble Sort", "Insertion Sort"],
value="Bubble Sort", label="Algorithm"
)
sort_btn = gr.Button("▶️ Run Algorithm", variant="primary")
data_output = gr.Textbox(label="Generated Data", interactive=True)
gen_btn.click(
visualizer.generate_data,
inputs=[data_size, data_type],
outputs=[data_output]
)
# Visualization Area
with gr.Column(scale=2, elem_classes="visualization-container"):
plot_output = gr.Image(label="Visualization", height=400)
pseudocode = gr.HTML(label="Pseudocode")
with gr.Row():
prev_btn = gr.Button("⏪ Previous Step")
next_btn = gr.Button("⏩ Next Step")
step_counter = gr.Number(value=0, label="Current Step", interactive=False)
sort_btn.click(
visualizer.visualize_sorting,
inputs=[algorithm, data_output],
outputs=[plot_output, pseudocode, error_output]
)
with gr.Tab("📊 Graph Algorithms"):
with gr.Row():
# Control Panel
with gr.Column(scale=1, elem_classes="control-panel"):
gr.Markdown("### Graph Definition")
graph_input = gr.Textbox(
value="A: B,C\nB: A,D\nC: A,E\nD: B\nE: C",
lines=7,
label="Adjacency List",
placeholder="Enter graph as:\nnode: neighbor1,neighbor2\n...",
)
with gr.Row():
start_node = gr.Textbox(value="A", label="Start Node")
algorithm = gr.Dropdown(
["DFS", "BFS"],
value="DFS", label="Algorithm"
)
graph_btn = gr.Button("▶️ Run Algorithm", variant="primary")
# Visualization Area
with gr.Column(scale=2, elem_classes="visualization-container"):
graph_plot = gr.Image(label="Graph Visualization", height=400)
graph_pseudocode = gr.HTML(label="Pseudocode")
with gr.Row():
graph_prev = gr.Button("⏪ Previous Step")
graph_next = gr.Button("⏩ Next Step")
graph_step = gr.Number(value=0, label="Current Step", interactive=False)
graph_btn.click(
visualizer.visualize_graph,
inputs=[algorithm, graph_input, start_node],
outputs=[graph_plot, graph_pseudocode, error_output]
)
with gr.Tab("📚 Complexity Analysis"):
gr.Markdown("""
## Algorithm Complexity Cheat Sheet
| Algorithm | Time (Best) | Time (Avg) | Time (Worst) | Space | Use Cases |
|-----------------|---------------|---------------|---------------|---------------|-------------------------------|
| **Bubble Sort** | O(n) | O(n²) | O(n²) | O(1) | Educational, small datasets |
| **Insertion Sort** | O(n) | O(n²) | O(n²) | O(1) | Small datasets, nearly sorted|
| **Merge Sort** | O(n log n) | O(n log n) | O(n log n) | O(n) | General purpose, stable |
| **Quick Sort** | O(n log n) | O(n log n) | O(n²) | O(log n) | Large datasets, in-place |
| **DFS** | O(V+E) | O(V+E) | O(V+E) | O(V) | Pathfinding, cycle detection |
| **BFS** | O(V+E) | O(V+E) | O(V+E) | O(V) | Shortest path, level order |
### Key Insights:
- **Sorting Algorithms**:
- Use **QuickSort** for average-case performance
- Use **MergeSort** for guaranteed O(n log n) performance
- **BubbleSort** and **InsertionSort** are efficient for small datasets
- **Graph Algorithms**:
- **DFS** is better for pathfinding in deep graphs
- **BFS** is better for shortest path in unweighted graphs
""")
with gr.Tab("💡 About"):
gr.Markdown("""
## About AlgoViz Pro
AlgoViz Pro is an interactive visualization tool for understanding Data Structures and Algorithms.
It provides step-by-step visualizations with pseudocode execution highlighting to help students
and developers understand how algorithms work.
**Features**:
- Real-time algorithm visualization
- Step-by-step execution control
- Multiple algorithm implementations
- Pseudocode with execution highlighting
- Customizable input data
- Comprehensive error handling
- Complexity analysis reference
**Algorithms Implemented**:
- Sorting: Bubble Sort, Insertion Sort
- Graph Traversal: DFS, BFS
**Future Enhancements**:
- Add more algorithms (Merge Sort, Dijkstra, etc.)
- Speed control for animations
- Comparison mode for algorithms
- Export visualizations as GIF
Developed with ❤️ using Python, Gradio, and Matplotlib
""")
# Navigation events
next_btn.click(
visualizer.next_step,
outputs=[plot_output, pseudocode, error_output]
)
prev_btn.click(
visualizer.prev_step,
outputs=[plot_output, pseudocode, error_output]
)
graph_next.click(
visualizer.next_step,
outputs=[graph_plot, graph_pseudocode, error_output]
)
graph_prev.click(
visualizer.prev_step,
outputs=[graph_plot, graph_pseudocode, error_output]
)
# Footer
gr.Markdown("""
<footer>
<p>AlgoViz Pro v1.1 | Robust & Error-Resistant | For educational purposes</p>
</footer>
""")
# Error handling display
def show_error(error_msg):
if error_msg:
return gr.update(value=f"⚠️ ERROR: {error_msg}", visible=True)
return gr.update(visible=False)
error_output.change(
show_error,
inputs=[error_output],
outputs=[error_output]
)
if __name__ == "__main__":
demo.launch()