Spaces:
Running
on
Zero
Running
on
Zero
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(""" | |
<div style=' | |
text-align: center; | |
padding: 20px 0; | |
margin: 15px 0; | |
background: linear-gradient(to right, rgba(66, 153, 225, 0.1), rgba(72, 187, 120, 0.1)); | |
border-radius: 10px; | |
'> | |
<p style=' | |
font-size: 1.2em; | |
margin: 0; | |
padding: 0 20px; | |
line-height: 1.5; | |
background: linear-gradient(90deg, #4299e1, #48bb78); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
font-weight: 600; | |
'> | |
Gain deeper insight into dog breed characteristics through visualization to help you make a more informed choice. | |
</p> | |
</div> | |
""") | |
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 | |