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 = "
" for i, line in enumerate(self.pseudocode): if i == active_line: html += f"
{line}
" else: html += f"
{line}
" html += "
" 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("""

🌟 AlgoViz Pro

Interactive Data Structures & Algorithms Visualization Platform

""") # 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(""" """) # 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()