import gradio as gr import matplotlib.pyplot as plt import numpy as np import sqlite3 from matplotlib.figure import Figure from typing import Dict, List, Optional, Tuple import pandas as pd from PIL import Image from dog_database import get_dog_description from scoring_calculation_system import UserPreferences, calculate_compatibility_score def create_visualization_tab(dog_breeds, get_dog_description, calculate_compatibility_score, UserPreferences): """Create a visualization tab for breed characteristic analysis""" # Create shared state container shared_preferences = gr.State({ "living_space": "apartment", "yard_access": "no_yard", "exercise_time": 60, "exercise_type": "moderate_activity", "grooming_commitment": "medium", "experience_level": "beginner", "noise_tolerance": "medium", "has_children": False, "children_age": "school_age", "climate": "moderate" }) gr.HTML("""

Gain deeper insight into dog breed characteristics through visualization to help you make a more informed choice.

""") with gr.Tabs(): # Single breed radar chart analysis tab with gr.TabItem("Breed Radar Chart Analysis"): with gr.Row(): with gr.Column(scale=1): # User interface components - Left side breed_choices = [(breed.replace('_', ' '), breed) for breed in sorted(dog_breeds)] breed_dropdown = gr.Dropdown( label="Select Breed", choices=breed_choices, value=breed_choices[0][1] if breed_choices else None, info="Select a breed to view its characteristics radar chart" ) with gr.Accordion("User Preferences (Affects Scoring)", open=False): living_space = gr.Radio( label="Living Space", choices=["apartment", "house_small", "house_large"], value="apartment", info="Your residential environment type" ) yard_access = gr.Radio( label="Yard Condition", choices=["no_yard", "shared_yard", "private_yard"], value="no_yard", info="Whether you have yard space" ) exercise_time = gr.Slider( label="Daily Exercise Time (minutes)", minimum=15, maximum=180, value=60, step=15, info="Daily exercise time you can provide" ) exercise_type = gr.Radio( label="Exercise Type", choices=["light_walks", "moderate_activity", "active_training"], value="moderate_activity", info="Your preferred exercise method" ) grooming_commitment = gr.Radio( label="Grooming Commitment", choices=["low", "medium", "high"], value="medium", info="Level of grooming care you're willing to provide" ) experience_level = gr.Radio( label="Experience Level", choices=["beginner", "intermediate", "advanced"], value="beginner", info="Your level of dog owning experience" ) noise_tolerance = gr.Radio( label="Noise Tolerance", choices=["low", "medium", "high"], value="medium", info="Your acceptance level of dog barking" ) has_children = gr.Checkbox( label="Have Children", value=False, info="Whether you have children at home" ) children_age = gr.Radio( label="Children's Age", choices=["toddler", "school_age", "teenager"], value="school_age", visible=False, info="Age group of children at home" ) climate = gr.Radio( label="Climate Environment", choices=["cold", "moderate", "hot"], value="moderate", info="Climate characteristics of your living area" ) # Listen for has_children changes to control children_age display has_children.change( fn=lambda x: gr.update(visible=x), inputs=has_children, outputs=children_age ) # Add function to update shared preferences def update_shared_preferences(*args): return { "living_space": args[0], "yard_access": args[1], "exercise_time": args[2], "exercise_type": args[3], "grooming_commitment": args[4], "experience_level": args[5], "noise_tolerance": args[6], "has_children": args[7], "children_age": args[8], "climate": args[9] } # Monitor preference changes and update shared state all_preferences = [living_space, yard_access, exercise_time, exercise_type, grooming_commitment, experience_level, noise_tolerance, has_children, children_age, climate] for pref in all_preferences: pref.change( update_shared_preferences, inputs=all_preferences, outputs=shared_preferences ) generate_btn = gr.Button("Generate Radar Chart", variant="primary") with gr.Column(scale=2): # Right display area radar_plot = gr.Plot(label="Breed Characteristics Radar Chart") breed_details = gr.JSON(label="Breed Detailed Information") # Button click event generate_btn.click( fn=lambda *args: generate_radar_chart( args[0], create_user_preferences(*args[1:]), get_dog_description, calculate_compatibility_score ), inputs=[breed_dropdown, living_space, yard_access, exercise_time, exercise_type, grooming_commitment, experience_level, noise_tolerance, has_children, children_age, climate], outputs=[radar_plot, breed_details] ) # Breed comparison analysis tab - Improved version with gr.TabItem("Breed Comparison Analysis"): with gr.Row(): breed1_dropdown = gr.Dropdown( label="Select First Breed", choices=breed_choices, value=breed_choices[0][1] if breed_choices else None ) breed2_dropdown = gr.Dropdown( label="Select Second Breed", choices=breed_choices, value=breed_choices[1][1] if len(breed_choices) > 1 else None ) with gr.Row(): use_shared_settings = gr.Checkbox( label="Use Radar Chart Analysis Settings", value=True, info="Check to use the same preferences from the Radar Chart Analysis tab" ) # Custom settings container - only visible when not using shared settings with gr.Column(visible=False) as custom_settings: with gr.Accordion("Custom Preferences", open=True): comp_living_space = gr.Radio( label="Living Space", choices=["apartment", "house_small", "house_large"], value="apartment" ) comp_yard_access = gr.Radio( label="Yard Condition", choices=["no_yard", "shared_yard", "private_yard"], value="no_yard" ) comp_exercise_time = gr.Slider( label="Daily Exercise Time (minutes)", minimum=15, maximum=180, value=60, step=15 ) comp_exercise_type = gr.Radio( label="Exercise Type", choices=["light_walks", "moderate_activity", "active_training"], value="moderate_activity" ) # Toggle custom settings visibility based on checkbox use_shared_settings.change( fn=lambda x: gr.update(visible=not x), inputs=use_shared_settings, outputs=custom_settings ) compare_btn = gr.Button("Compare Breeds", variant="primary") comparison_plot = gr.Plot(label="Breed Characteristics Comparison") # Improved comparison function that handles both shared and custom settings def get_comparison_settings(use_shared, shared_prefs, *custom_prefs): """ Select appropriate settings based on user choice Args: use_shared: Boolean indicating whether to use shared settings shared_prefs: Dictionary of shared preferences custom_prefs: Custom preference values if not using shared Returns: UserPreferences object with the selected settings """ if use_shared: # Use settings from Radar Chart tab return create_user_preferences_from_dict(shared_prefs) else: # Use custom settings from Comparison tab return create_user_preferences( custom_prefs[0], custom_prefs[1], custom_prefs[2], custom_prefs[3], "medium", "beginner", "medium", False, "school_age", "moderate" ) # Connect the comparison button compare_btn.click( fn=lambda breed1, breed2, use_shared, shared_prefs, *custom_prefs: generate_comparison_chart( breed1, breed2, get_comparison_settings(use_shared, shared_prefs, *custom_prefs), get_dog_description, calculate_compatibility_score ), inputs=[ breed1_dropdown, breed2_dropdown, use_shared_settings, shared_preferences, comp_living_space, comp_yard_access, comp_exercise_time, comp_exercise_type ], outputs=comparison_plot ) return None def create_user_preferences(living_space, yard_access, exercise_time, exercise_type, grooming_commitment, experience_level, noise_tolerance, has_children, children_age, climate): """ Create UserPreferences object from UI inputs Args: living_space: Type of living environment yard_access: Yard availability exercise_time: Minutes of daily exercise exercise_type: Type of exercise activity grooming_commitment: Level of grooming commitment experience_level: Dog owner experience level noise_tolerance: Tolerance for barking has_children: Whether there are children in the home children_age: Age group of children climate: Climate type of the living area Returns: UserPreferences object with the specified settings """ return UserPreferences( living_space=living_space, yard_access=yard_access, exercise_time=exercise_time, exercise_type=exercise_type, grooming_commitment=grooming_commitment, experience_level=experience_level, time_availability="moderate", # Default value has_children=has_children, children_age=children_age if has_children else "school_age", noise_tolerance=noise_tolerance, space_for_play=True, # Default value other_pets=False, # Default value climate=climate ) def create_user_preferences_from_dict(prefs_dict): """ Create UserPreferences object from a dictionary Args: prefs_dict: Dictionary containing preference values Returns: UserPreferences object populated with the dictionary values """ return UserPreferences( living_space=prefs_dict["living_space"], yard_access=prefs_dict["yard_access"], exercise_time=prefs_dict["exercise_time"], exercise_type=prefs_dict["exercise_type"], grooming_commitment=prefs_dict["grooming_commitment"], experience_level=prefs_dict["experience_level"], time_availability="moderate", # Default value has_children=prefs_dict["has_children"], children_age=prefs_dict["children_age"], noise_tolerance=prefs_dict["noise_tolerance"], space_for_play=True, # Default value other_pets=False, # Default value climate=prefs_dict["climate"] ) def generate_radar_chart(breed_name, user_prefs, get_dog_description, calculate_compatibility_score): """ Generate radar chart for a single breed Args: breed_name: Dog breed name user_prefs: UserPreferences object get_dog_description: Function to get breed description calculate_compatibility_score: Function to calculate compatibility score Returns: tuple: (matplotlib figure, breed description dict) """ try: # Get breed description breed_info = get_dog_description(breed_name) if not breed_info: # Create empty figure with error message fig = Figure(figsize=(8, 8)) ax = fig.add_subplot(111) ax.text(0.5, 0.5, f"No information found for breed: {breed_name}", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=14) ax.axis('off') return fig, {"error": f"No information found for breed: {breed_name}"} # Calculate compatibility scores scores = calculate_compatibility_score(breed_info, user_prefs) # Prepare data for radar chart categories = ['Space Compatibility', 'Exercise Needs', 'Grooming', 'Experience Required', 'Health', 'Noise Level'] values = [scores['space'], scores['exercise'], scores['grooming'], scores['experience'], scores['health'], scores['noise']] # Close the polygon by appending first value values_closed = values + [values[0]] categories_closed = categories + [categories[0]] # Calculate angles for each category angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False).tolist() angles += angles[:1] # Close the loop # Create figure and polar axis fig = Figure(figsize=(10, 8)) ax = fig.add_subplot(111, polar=True) # Plot data ax.fill(angles, values_closed, color='skyblue', alpha=0.25) ax.plot(angles, values_closed, color='blue', linewidth=2) # Add category labels ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories, fontsize=12) # Configure y-axis ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0]) ax.set_yticklabels(['0.2', '0.4', '0.6', '0.8', '1.0'], fontsize=10) ax.set_ylim(0, 1) # Add a title breed_display_name = breed_name.replace('_', ' ') ax.set_title(f"{breed_display_name} Characteristic Scores", fontsize=16, pad=20) # Add value labels at each point for i, (angle, value) in enumerate(zip(angles[:-1], values)): ax.text(angle, value + 0.05, f"{value:.2f}", ha='center', va='center', fontsize=10, bbox=dict(facecolor='white', alpha=0.7, boxstyle="round,pad=0.3")) # Add grid ax.grid(True, linestyle='--', alpha=0.7) # Add overall score text overall_score = scores.get('overall', 0) fig.text(0.5, 0.02, f"Overall Match Score: {overall_score:.2f}", ha='center', fontsize=14, bbox=dict(facecolor='lightgreen', alpha=0.3, boxstyle="round,pad=0.5")) # Enhance aesthetics fig.patch.set_facecolor('#f8f9fa') ax.set_facecolor('#f0f0f0') # Print debug information print(f"Generated radar chart for {breed_name}") print(f"Scores: {scores}") return fig, breed_info except Exception as e: # Create empty figure with error message fig = Figure(figsize=(8, 8)) ax = fig.add_subplot(111) ax.text(0.5, 0.5, f"Error generating chart: {str(e)}", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=14) ax.axis('off') print(f"Error in generate_radar_chart: {str(e)}") return fig, {"error": f"Error generating chart: {str(e)}"} def generate_comparison_chart(breed1, breed2, user_prefs, get_dog_description, calculate_compatibility_score): """ Generate comparison chart for two breeds Args: breed1, breed2: Dog breed names user_prefs: UserPreferences object get_dog_description: Function to get breed description calculate_compatibility_score: Function to calculate compatibility score Returns: matplotlib figure: Comparison chart """ try: # Get breed descriptions breed1_info = get_dog_description(breed1) breed2_info = get_dog_description(breed2) if not breed1_info or not breed2_info: # Create empty figure with error message fig = Figure(figsize=(10, 6)) ax = fig.add_subplot(111) ax.text(0.5, 0.5, f"Missing breed information. Please check both breeds.", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=14) ax.axis('off') return fig # Calculate compatibility scores scores1 = calculate_compatibility_score(breed1_info, user_prefs) scores2 = calculate_compatibility_score(breed2_info, user_prefs) # Prepare data for bar chart categories = ['Space Compatibility', 'Exercise Needs', 'Grooming', 'Experience Required', 'Health', 'Noise Level'] values1 = [scores1['space'], scores1['exercise'], scores1['grooming'], scores1['experience'], scores1['health'], scores1['noise']] values2 = [scores2['space'], scores2['exercise'], scores2['grooming'], scores2['experience'], scores2['health'], scores2['noise']] # Create figure fig = Figure(figsize=(12, 7)) ax = fig.add_subplot(111) # Set width of bars x = np.arange(len(categories)) width = 0.35 # Plot bars breed1_display = breed1.replace('_', ' ') breed2_display = breed2.replace('_', ' ') rects1 = ax.bar(x - width/2, values1, width, label=breed1_display, color='#4299e1') rects2 = ax.bar(x + width/2, values2, width, label=breed2_display, color='#f56565') # Add labels and title ax.set_xlabel('Scoring Dimensions', fontsize=12) ax.set_ylabel('Score (0-1)', fontsize=12) ax.set_title(f'{breed1_display} vs {breed2_display} Breed Comparison', fontsize=15) ax.set_xticks(x) ax.set_xticklabels(categories, rotation=30, ha='right') ax.legend(loc='upper right') # Add value labels on top of bars def add_labels(rects): for rect in rects: height = rect.get_height() ax.annotate(f'{height:.2f}', xy=(rect.get_x() + rect.get_width() / 2, height), xytext=(0, 3), # 3 points vertical offset textcoords="offset points", ha='center', va='bottom', fontsize=9, fontweight='bold') add_labels(rects1) add_labels(rects2) # Set y-axis limit ax.set_ylim(0, 1.1) # Add grid ax.grid(True, linestyle='--', alpha=0.3, axis='y') # Add overall score comparison overall1 = scores1.get('overall', 0) overall2 = scores2.get('overall', 0) fig.text(0.5, 0.02, f"Overall Match Scores: {breed1_display}: {overall1:.2f} | {breed2_display}: {overall2:.2f}", ha='center', fontsize=13, bbox=dict(facecolor='#edf2f7', alpha=0.7, boxstyle="round,pad=0.5")) # Enhance aesthetics fig.patch.set_facecolor('#f8f9fa') ax.set_facecolor('#f0f0f0') # Add a tight layout to ensure everything fits fig.tight_layout(rect=[0, 0.05, 1, 0.95]) # Print debug information print(f"Generated comparison chart for {breed1} vs {breed2}") return fig except Exception as e: # Create empty figure with error message fig = Figure(figsize=(10, 6)) ax = fig.add_subplot(111) ax.text(0.5, 0.5, f"Error generating comparison: {str(e)}", horizontalalignment='center', verticalalignment='center', transform=ax.transAxes, fontsize=14) ax.axis('off') print(f"Error in generate_comparison_chart: {str(e)}") return fig