Spaces:
Running
on
Zero
Running
on
Zero
Upload breed_visualization.py
Browse files- breed_visualization.py +570 -0
breed_visualization.py
ADDED
@@ -0,0 +1,570 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import matplotlib.pyplot as plt
|
3 |
+
import numpy as np
|
4 |
+
import sqlite3
|
5 |
+
from matplotlib.figure import Figure
|
6 |
+
from typing import Dict, List, Optional, Tuple
|
7 |
+
import pandas as pd
|
8 |
+
from PIL import Image
|
9 |
+
|
10 |
+
def create_visualization_tab(dog_breeds, get_dog_description, calculate_compatibility_score, UserPreferences):
|
11 |
+
"""Create a visualization tab for breed characteristic analysis"""
|
12 |
+
|
13 |
+
# Create shared state container
|
14 |
+
shared_preferences = gr.State({
|
15 |
+
"living_space": "apartment",
|
16 |
+
"yard_access": "no_yard",
|
17 |
+
"exercise_time": 60,
|
18 |
+
"exercise_type": "moderate_activity",
|
19 |
+
"grooming_commitment": "medium",
|
20 |
+
"experience_level": "beginner",
|
21 |
+
"noise_tolerance": "medium",
|
22 |
+
"has_children": False,
|
23 |
+
"children_age": "school_age",
|
24 |
+
"climate": "moderate"
|
25 |
+
})
|
26 |
+
|
27 |
+
gr.HTML("""
|
28 |
+
<div style='
|
29 |
+
text-align: center;
|
30 |
+
padding: 20px 0;
|
31 |
+
margin: 15px 0;
|
32 |
+
background: linear-gradient(to right, rgba(66, 153, 225, 0.1), rgba(72, 187, 120, 0.1));
|
33 |
+
border-radius: 10px;
|
34 |
+
'>
|
35 |
+
<p style='
|
36 |
+
font-size: 1.2em;
|
37 |
+
margin: 0;
|
38 |
+
padding: 0 20px;
|
39 |
+
line-height: 1.5;
|
40 |
+
background: linear-gradient(90deg, #4299e1, #48bb78);
|
41 |
+
-webkit-background-clip: text;
|
42 |
+
-webkit-text-fill-color: transparent;
|
43 |
+
font-weight: 600;
|
44 |
+
'>
|
45 |
+
Gain deeper insight into dog breed characteristics through visualization to help you make a more informed choice.
|
46 |
+
</p>
|
47 |
+
</div>
|
48 |
+
""")
|
49 |
+
|
50 |
+
with gr.Tabs():
|
51 |
+
# Single breed radar chart analysis tab
|
52 |
+
with gr.TabItem("Breed Radar Chart Analysis"):
|
53 |
+
with gr.Row():
|
54 |
+
with gr.Column(scale=1):
|
55 |
+
# User interface components - Left side
|
56 |
+
breed_choices = [(breed.replace('_', ' '), breed) for breed in sorted(dog_breeds)]
|
57 |
+
|
58 |
+
breed_dropdown = gr.Dropdown(
|
59 |
+
label="Select Breed",
|
60 |
+
choices=breed_choices,
|
61 |
+
value=breed_choices[0][1] if breed_choices else None,
|
62 |
+
info="Select a breed to view its characteristics radar chart"
|
63 |
+
)
|
64 |
+
|
65 |
+
with gr.Accordion("User Preferences (Affects Scoring)", open=False):
|
66 |
+
living_space = gr.Radio(
|
67 |
+
label="Living Space",
|
68 |
+
choices=["apartment", "house_small", "house_large"],
|
69 |
+
value="apartment",
|
70 |
+
info="Your residential environment type"
|
71 |
+
)
|
72 |
+
|
73 |
+
yard_access = gr.Radio(
|
74 |
+
label="Yard Condition",
|
75 |
+
choices=["no_yard", "shared_yard", "private_yard"],
|
76 |
+
value="no_yard",
|
77 |
+
info="Whether you have yard space"
|
78 |
+
)
|
79 |
+
|
80 |
+
exercise_time = gr.Slider(
|
81 |
+
label="Daily Exercise Time (minutes)",
|
82 |
+
minimum=15,
|
83 |
+
maximum=180,
|
84 |
+
value=60,
|
85 |
+
step=15,
|
86 |
+
info="Daily exercise time you can provide"
|
87 |
+
)
|
88 |
+
|
89 |
+
exercise_type = gr.Radio(
|
90 |
+
label="Exercise Type",
|
91 |
+
choices=["light_walks", "moderate_activity", "active_training"],
|
92 |
+
value="moderate_activity",
|
93 |
+
info="Your preferred exercise method"
|
94 |
+
)
|
95 |
+
|
96 |
+
grooming_commitment = gr.Radio(
|
97 |
+
label="Grooming Commitment",
|
98 |
+
choices=["low", "medium", "high"],
|
99 |
+
value="medium",
|
100 |
+
info="Level of grooming care you're willing to provide"
|
101 |
+
)
|
102 |
+
|
103 |
+
experience_level = gr.Radio(
|
104 |
+
label="Experience Level",
|
105 |
+
choices=["beginner", "intermediate", "advanced"],
|
106 |
+
value="beginner",
|
107 |
+
info="Your level of dog owning experience"
|
108 |
+
)
|
109 |
+
|
110 |
+
noise_tolerance = gr.Radio(
|
111 |
+
label="Noise Tolerance",
|
112 |
+
choices=["low", "medium", "high"],
|
113 |
+
value="medium",
|
114 |
+
info="Your acceptance level of dog barking"
|
115 |
+
)
|
116 |
+
|
117 |
+
has_children = gr.Checkbox(
|
118 |
+
label="Have Children",
|
119 |
+
value=False,
|
120 |
+
info="Whether you have children at home"
|
121 |
+
)
|
122 |
+
|
123 |
+
children_age = gr.Radio(
|
124 |
+
label="Children's Age",
|
125 |
+
choices=["toddler", "school_age", "teenager"],
|
126 |
+
value="school_age",
|
127 |
+
visible=False,
|
128 |
+
info="Age group of children at home"
|
129 |
+
)
|
130 |
+
|
131 |
+
climate = gr.Radio(
|
132 |
+
label="Climate Environment",
|
133 |
+
choices=["cold", "moderate", "hot"],
|
134 |
+
value="moderate",
|
135 |
+
info="Climate characteristics of your living area"
|
136 |
+
)
|
137 |
+
|
138 |
+
# Listen for has_children changes to control children_age display
|
139 |
+
has_children.change(
|
140 |
+
fn=lambda x: gr.update(visible=x),
|
141 |
+
inputs=has_children,
|
142 |
+
outputs=children_age
|
143 |
+
)
|
144 |
+
|
145 |
+
# Add function to update shared preferences
|
146 |
+
def update_shared_preferences(*args):
|
147 |
+
return {
|
148 |
+
"living_space": args[0],
|
149 |
+
"yard_access": args[1],
|
150 |
+
"exercise_time": args[2],
|
151 |
+
"exercise_type": args[3],
|
152 |
+
"grooming_commitment": args[4],
|
153 |
+
"experience_level": args[5],
|
154 |
+
"noise_tolerance": args[6],
|
155 |
+
"has_children": args[7],
|
156 |
+
"children_age": args[8],
|
157 |
+
"climate": args[9]
|
158 |
+
}
|
159 |
+
|
160 |
+
# Monitor preference changes and update shared state
|
161 |
+
all_preferences = [living_space, yard_access, exercise_time,
|
162 |
+
exercise_type, grooming_commitment, experience_level,
|
163 |
+
noise_tolerance, has_children, children_age, climate]
|
164 |
+
|
165 |
+
for pref in all_preferences:
|
166 |
+
pref.change(
|
167 |
+
update_shared_preferences,
|
168 |
+
inputs=all_preferences,
|
169 |
+
outputs=shared_preferences
|
170 |
+
)
|
171 |
+
|
172 |
+
generate_btn = gr.Button("Generate Radar Chart", variant="primary")
|
173 |
+
|
174 |
+
with gr.Column(scale=2):
|
175 |
+
# Right display area
|
176 |
+
radar_plot = gr.Plot(label="Breed Characteristics Radar Chart")
|
177 |
+
breed_details = gr.JSON(label="Breed Detailed Information")
|
178 |
+
|
179 |
+
# Button click event
|
180 |
+
generate_btn.click(
|
181 |
+
fn=lambda *args: generate_radar_chart(
|
182 |
+
args[0], create_user_preferences(*args[1:]),
|
183 |
+
get_dog_description, calculate_compatibility_score
|
184 |
+
),
|
185 |
+
inputs=[breed_dropdown, living_space, yard_access, exercise_time,
|
186 |
+
exercise_type, grooming_commitment, experience_level,
|
187 |
+
noise_tolerance, has_children, children_age, climate],
|
188 |
+
outputs=[radar_plot, breed_details]
|
189 |
+
)
|
190 |
+
|
191 |
+
# Breed comparison analysis tab - Improved version
|
192 |
+
with gr.TabItem("Breed Comparison Analysis"):
|
193 |
+
with gr.Row():
|
194 |
+
breed1_dropdown = gr.Dropdown(
|
195 |
+
label="Select First Breed",
|
196 |
+
choices=breed_choices,
|
197 |
+
value=breed_choices[0][1] if breed_choices else None
|
198 |
+
)
|
199 |
+
|
200 |
+
breed2_dropdown = gr.Dropdown(
|
201 |
+
label="Select Second Breed",
|
202 |
+
choices=breed_choices,
|
203 |
+
value=breed_choices[1][1] if len(breed_choices) > 1 else None
|
204 |
+
)
|
205 |
+
|
206 |
+
with gr.Row():
|
207 |
+
use_shared_settings = gr.Checkbox(
|
208 |
+
label="Use Radar Chart Analysis Settings",
|
209 |
+
value=True,
|
210 |
+
info="Check to use the same preferences from the Radar Chart Analysis tab"
|
211 |
+
)
|
212 |
+
|
213 |
+
# Custom settings container - only visible when not using shared settings
|
214 |
+
with gr.Column(visible=False) as custom_settings:
|
215 |
+
with gr.Accordion("Custom Preferences", open=True):
|
216 |
+
comp_living_space = gr.Radio(
|
217 |
+
label="Living Space",
|
218 |
+
choices=["apartment", "house_small", "house_large"],
|
219 |
+
value="apartment"
|
220 |
+
)
|
221 |
+
|
222 |
+
comp_yard_access = gr.Radio(
|
223 |
+
label="Yard Condition",
|
224 |
+
choices=["no_yard", "shared_yard", "private_yard"],
|
225 |
+
value="no_yard"
|
226 |
+
)
|
227 |
+
|
228 |
+
comp_exercise_time = gr.Slider(
|
229 |
+
label="Daily Exercise Time (minutes)",
|
230 |
+
minimum=15,
|
231 |
+
maximum=180,
|
232 |
+
value=60,
|
233 |
+
step=15
|
234 |
+
)
|
235 |
+
|
236 |
+
comp_exercise_type = gr.Radio(
|
237 |
+
label="Exercise Type",
|
238 |
+
choices=["light_walks", "moderate_activity", "active_training"],
|
239 |
+
value="moderate_activity"
|
240 |
+
)
|
241 |
+
|
242 |
+
# Toggle custom settings visibility based on checkbox
|
243 |
+
use_shared_settings.change(
|
244 |
+
fn=lambda x: gr.update(visible=not x),
|
245 |
+
inputs=use_shared_settings,
|
246 |
+
outputs=custom_settings
|
247 |
+
)
|
248 |
+
|
249 |
+
compare_btn = gr.Button("Compare Breeds", variant="primary")
|
250 |
+
comparison_plot = gr.Plot(label="Breed Characteristics Comparison")
|
251 |
+
|
252 |
+
# Improved comparison function that handles both shared and custom settings
|
253 |
+
def get_comparison_settings(use_shared, shared_prefs, *custom_prefs):
|
254 |
+
"""
|
255 |
+
Select appropriate settings based on user choice
|
256 |
+
|
257 |
+
Args:
|
258 |
+
use_shared: Boolean indicating whether to use shared settings
|
259 |
+
shared_prefs: Dictionary of shared preferences
|
260 |
+
custom_prefs: Custom preference values if not using shared
|
261 |
+
|
262 |
+
Returns:
|
263 |
+
UserPreferences object with the selected settings
|
264 |
+
"""
|
265 |
+
if use_shared:
|
266 |
+
# Use settings from Radar Chart tab
|
267 |
+
return create_user_preferences_from_dict(shared_prefs)
|
268 |
+
else:
|
269 |
+
# Use custom settings from Comparison tab
|
270 |
+
return create_user_preferences(
|
271 |
+
custom_prefs[0], custom_prefs[1], custom_prefs[2], custom_prefs[3],
|
272 |
+
"medium", "beginner", "medium", False, "school_age", "moderate"
|
273 |
+
)
|
274 |
+
|
275 |
+
# Connect the comparison button
|
276 |
+
compare_btn.click(
|
277 |
+
fn=lambda breed1, breed2, use_shared, shared_prefs, *custom_prefs: generate_comparison_chart(
|
278 |
+
breed1, breed2,
|
279 |
+
get_comparison_settings(use_shared, shared_prefs, *custom_prefs),
|
280 |
+
get_dog_description, calculate_compatibility_score
|
281 |
+
),
|
282 |
+
inputs=[
|
283 |
+
breed1_dropdown, breed2_dropdown,
|
284 |
+
use_shared_settings, shared_preferences,
|
285 |
+
comp_living_space, comp_yard_access,
|
286 |
+
comp_exercise_time, comp_exercise_type
|
287 |
+
],
|
288 |
+
outputs=comparison_plot
|
289 |
+
)
|
290 |
+
|
291 |
+
return None
|
292 |
+
|
293 |
+
def create_user_preferences(living_space, yard_access, exercise_time, exercise_type,
|
294 |
+
grooming_commitment, experience_level, noise_tolerance,
|
295 |
+
has_children, children_age, climate):
|
296 |
+
"""
|
297 |
+
Create UserPreferences object from UI inputs
|
298 |
+
|
299 |
+
Args:
|
300 |
+
living_space: Type of living environment
|
301 |
+
yard_access: Yard availability
|
302 |
+
exercise_time: Minutes of daily exercise
|
303 |
+
exercise_type: Type of exercise activity
|
304 |
+
grooming_commitment: Level of grooming commitment
|
305 |
+
experience_level: Dog owner experience level
|
306 |
+
noise_tolerance: Tolerance for barking
|
307 |
+
has_children: Whether there are children in the home
|
308 |
+
children_age: Age group of children
|
309 |
+
climate: Climate type of the living area
|
310 |
+
|
311 |
+
Returns:
|
312 |
+
UserPreferences object with the specified settings
|
313 |
+
"""
|
314 |
+
return UserPreferences(
|
315 |
+
living_space=living_space,
|
316 |
+
yard_access=yard_access,
|
317 |
+
exercise_time=exercise_time,
|
318 |
+
exercise_type=exercise_type,
|
319 |
+
grooming_commitment=grooming_commitment,
|
320 |
+
experience_level=experience_level,
|
321 |
+
time_availability="moderate", # Default value
|
322 |
+
has_children=has_children,
|
323 |
+
children_age=children_age if has_children else "school_age",
|
324 |
+
noise_tolerance=noise_tolerance,
|
325 |
+
space_for_play=True, # Default value
|
326 |
+
other_pets=False, # Default value
|
327 |
+
climate=climate
|
328 |
+
)
|
329 |
+
|
330 |
+
def create_user_preferences_from_dict(prefs_dict):
|
331 |
+
"""
|
332 |
+
Create UserPreferences object from a dictionary
|
333 |
+
|
334 |
+
Args:
|
335 |
+
prefs_dict: Dictionary containing preference values
|
336 |
+
|
337 |
+
Returns:
|
338 |
+
UserPreferences object populated with the dictionary values
|
339 |
+
"""
|
340 |
+
return UserPreferences(
|
341 |
+
living_space=prefs_dict["living_space"],
|
342 |
+
yard_access=prefs_dict["yard_access"],
|
343 |
+
exercise_time=prefs_dict["exercise_time"],
|
344 |
+
exercise_type=prefs_dict["exercise_type"],
|
345 |
+
grooming_commitment=prefs_dict["grooming_commitment"],
|
346 |
+
experience_level=prefs_dict["experience_level"],
|
347 |
+
time_availability="moderate", # Default value
|
348 |
+
has_children=prefs_dict["has_children"],
|
349 |
+
children_age=prefs_dict["children_age"],
|
350 |
+
noise_tolerance=prefs_dict["noise_tolerance"],
|
351 |
+
space_for_play=True, # Default value
|
352 |
+
other_pets=False, # Default value
|
353 |
+
climate=prefs_dict["climate"]
|
354 |
+
)
|
355 |
+
|
356 |
+
def generate_radar_chart(breed_name, user_prefs, get_dog_description, calculate_compatibility_score):
|
357 |
+
"""
|
358 |
+
Generate radar chart for a single breed
|
359 |
+
|
360 |
+
Args:
|
361 |
+
breed_name: Dog breed name
|
362 |
+
user_prefs: UserPreferences object
|
363 |
+
get_dog_description: Function to get breed description
|
364 |
+
calculate_compatibility_score: Function to calculate compatibility score
|
365 |
+
|
366 |
+
Returns:
|
367 |
+
tuple: (matplotlib figure, breed description dict)
|
368 |
+
"""
|
369 |
+
try:
|
370 |
+
# Get breed description
|
371 |
+
breed_info = get_dog_description(breed_name)
|
372 |
+
|
373 |
+
if not breed_info:
|
374 |
+
# Create empty figure with error message
|
375 |
+
fig = Figure(figsize=(8, 8))
|
376 |
+
ax = fig.add_subplot(111)
|
377 |
+
ax.text(0.5, 0.5, f"No information found for breed: {breed_name}",
|
378 |
+
horizontalalignment='center', verticalalignment='center',
|
379 |
+
transform=ax.transAxes, fontsize=14)
|
380 |
+
ax.axis('off')
|
381 |
+
return fig, {"error": f"No information found for breed: {breed_name}"}
|
382 |
+
|
383 |
+
# Calculate compatibility scores
|
384 |
+
scores = calculate_compatibility_score(breed_info, user_prefs)
|
385 |
+
|
386 |
+
# Prepare data for radar chart
|
387 |
+
categories = ['Space Compatibility', 'Exercise Needs', 'Grooming',
|
388 |
+
'Experience Required', 'Health', 'Noise Level']
|
389 |
+
values = [scores['space'], scores['exercise'], scores['grooming'],
|
390 |
+
scores['experience'], scores['health'], scores['noise']]
|
391 |
+
|
392 |
+
# Close the polygon by appending first value
|
393 |
+
values_closed = values + [values[0]]
|
394 |
+
categories_closed = categories + [categories[0]]
|
395 |
+
|
396 |
+
# Calculate angles for each category
|
397 |
+
angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False).tolist()
|
398 |
+
angles += angles[:1] # Close the loop
|
399 |
+
|
400 |
+
# Create figure and polar axis
|
401 |
+
fig = Figure(figsize=(10, 8))
|
402 |
+
ax = fig.add_subplot(111, polar=True)
|
403 |
+
|
404 |
+
# Plot data
|
405 |
+
ax.fill(angles, values_closed, color='skyblue', alpha=0.25)
|
406 |
+
ax.plot(angles, values_closed, color='blue', linewidth=2)
|
407 |
+
|
408 |
+
# Add category labels
|
409 |
+
ax.set_xticks(angles[:-1])
|
410 |
+
ax.set_xticklabels(categories, fontsize=12)
|
411 |
+
|
412 |
+
# Configure y-axis
|
413 |
+
ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0])
|
414 |
+
ax.set_yticklabels(['0.2', '0.4', '0.6', '0.8', '1.0'], fontsize=10)
|
415 |
+
ax.set_ylim(0, 1)
|
416 |
+
|
417 |
+
# Add a title
|
418 |
+
breed_display_name = breed_name.replace('_', ' ')
|
419 |
+
ax.set_title(f"{breed_display_name} Characteristic Scores", fontsize=16, pad=20)
|
420 |
+
|
421 |
+
# Add value labels at each point
|
422 |
+
for i, (angle, value) in enumerate(zip(angles[:-1], values)):
|
423 |
+
ax.text(angle, value + 0.05, f"{value:.2f}",
|
424 |
+
ha='center', va='center', fontsize=10,
|
425 |
+
bbox=dict(facecolor='white', alpha=0.7, boxstyle="round,pad=0.3"))
|
426 |
+
|
427 |
+
# Add grid
|
428 |
+
ax.grid(True, linestyle='--', alpha=0.7)
|
429 |
+
|
430 |
+
# Add overall score text
|
431 |
+
overall_score = scores.get('overall', 0)
|
432 |
+
fig.text(0.5, 0.02, f"Overall Match Score: {overall_score:.2f}",
|
433 |
+
ha='center', fontsize=14,
|
434 |
+
bbox=dict(facecolor='lightgreen', alpha=0.3, boxstyle="round,pad=0.5"))
|
435 |
+
|
436 |
+
# Enhance aesthetics
|
437 |
+
fig.patch.set_facecolor('#f8f9fa')
|
438 |
+
ax.set_facecolor('#f0f0f0')
|
439 |
+
|
440 |
+
# Print debug information
|
441 |
+
print(f"Generated radar chart for {breed_name}")
|
442 |
+
print(f"Scores: {scores}")
|
443 |
+
|
444 |
+
return fig, breed_info
|
445 |
+
|
446 |
+
except Exception as e:
|
447 |
+
# Create empty figure with error message
|
448 |
+
fig = Figure(figsize=(8, 8))
|
449 |
+
ax = fig.add_subplot(111)
|
450 |
+
ax.text(0.5, 0.5, f"Error generating chart: {str(e)}",
|
451 |
+
horizontalalignment='center', verticalalignment='center',
|
452 |
+
transform=ax.transAxes, fontsize=14)
|
453 |
+
ax.axis('off')
|
454 |
+
print(f"Error in generate_radar_chart: {str(e)}")
|
455 |
+
return fig, {"error": f"Error generating chart: {str(e)}"}
|
456 |
+
|
457 |
+
def generate_comparison_chart(breed1, breed2, user_prefs, get_dog_description, calculate_compatibility_score):
|
458 |
+
"""
|
459 |
+
Generate comparison chart for two breeds
|
460 |
+
|
461 |
+
Args:
|
462 |
+
breed1, breed2: Dog breed names
|
463 |
+
user_prefs: UserPreferences object
|
464 |
+
get_dog_description: Function to get breed description
|
465 |
+
calculate_compatibility_score: Function to calculate compatibility score
|
466 |
+
|
467 |
+
Returns:
|
468 |
+
matplotlib figure: Comparison chart
|
469 |
+
"""
|
470 |
+
try:
|
471 |
+
# Get breed descriptions
|
472 |
+
breed1_info = get_dog_description(breed1)
|
473 |
+
breed2_info = get_dog_description(breed2)
|
474 |
+
|
475 |
+
if not breed1_info or not breed2_info:
|
476 |
+
# Create empty figure with error message
|
477 |
+
fig = Figure(figsize=(10, 6))
|
478 |
+
ax = fig.add_subplot(111)
|
479 |
+
ax.text(0.5, 0.5, f"Missing breed information. Please check both breeds.",
|
480 |
+
horizontalalignment='center', verticalalignment='center',
|
481 |
+
transform=ax.transAxes, fontsize=14)
|
482 |
+
ax.axis('off')
|
483 |
+
return fig
|
484 |
+
|
485 |
+
# Calculate compatibility scores
|
486 |
+
scores1 = calculate_compatibility_score(breed1_info, user_prefs)
|
487 |
+
scores2 = calculate_compatibility_score(breed2_info, user_prefs)
|
488 |
+
|
489 |
+
# Prepare data for bar chart
|
490 |
+
categories = ['Space Compatibility', 'Exercise Needs', 'Grooming',
|
491 |
+
'Experience Required', 'Health', 'Noise Level']
|
492 |
+
values1 = [scores1['space'], scores1['exercise'], scores1['grooming'],
|
493 |
+
scores1['experience'], scores1['health'], scores1['noise']]
|
494 |
+
values2 = [scores2['space'], scores2['exercise'], scores2['grooming'],
|
495 |
+
scores2['experience'], scores2['health'], scores2['noise']]
|
496 |
+
|
497 |
+
# Create figure
|
498 |
+
fig = Figure(figsize=(12, 7))
|
499 |
+
ax = fig.add_subplot(111)
|
500 |
+
|
501 |
+
# Set width of bars
|
502 |
+
x = np.arange(len(categories))
|
503 |
+
width = 0.35
|
504 |
+
|
505 |
+
# Plot bars
|
506 |
+
breed1_display = breed1.replace('_', ' ')
|
507 |
+
breed2_display = breed2.replace('_', ' ')
|
508 |
+
|
509 |
+
rects1 = ax.bar(x - width/2, values1, width, label=breed1_display, color='#4299e1')
|
510 |
+
rects2 = ax.bar(x + width/2, values2, width, label=breed2_display, color='#f56565')
|
511 |
+
|
512 |
+
# Add labels and title
|
513 |
+
ax.set_xlabel('Scoring Dimensions', fontsize=12)
|
514 |
+
ax.set_ylabel('Score (0-1)', fontsize=12)
|
515 |
+
ax.set_title(f'{breed1_display} vs {breed2_display} Breed Comparison', fontsize=15)
|
516 |
+
ax.set_xticks(x)
|
517 |
+
ax.set_xticklabels(categories, rotation=30, ha='right')
|
518 |
+
ax.legend(loc='upper right')
|
519 |
+
|
520 |
+
# Add value labels on top of bars
|
521 |
+
def add_labels(rects):
|
522 |
+
for rect in rects:
|
523 |
+
height = rect.get_height()
|
524 |
+
ax.annotate(f'{height:.2f}',
|
525 |
+
xy=(rect.get_x() + rect.get_width() / 2, height),
|
526 |
+
xytext=(0, 3), # 3 points vertical offset
|
527 |
+
textcoords="offset points",
|
528 |
+
ha='center', va='bottom',
|
529 |
+
fontsize=9, fontweight='bold')
|
530 |
+
|
531 |
+
add_labels(rects1)
|
532 |
+
add_labels(rects2)
|
533 |
+
|
534 |
+
# Set y-axis limit
|
535 |
+
ax.set_ylim(0, 1.1)
|
536 |
+
|
537 |
+
# Add grid
|
538 |
+
ax.grid(True, linestyle='--', alpha=0.3, axis='y')
|
539 |
+
|
540 |
+
# Add overall score comparison
|
541 |
+
overall1 = scores1.get('overall', 0)
|
542 |
+
overall2 = scores2.get('overall', 0)
|
543 |
+
|
544 |
+
fig.text(0.5, 0.02,
|
545 |
+
f"Overall Match Scores: {breed1_display}: {overall1:.2f} | {breed2_display}: {overall2:.2f}",
|
546 |
+
ha='center', fontsize=13,
|
547 |
+
bbox=dict(facecolor='#edf2f7', alpha=0.7, boxstyle="round,pad=0.5"))
|
548 |
+
|
549 |
+
# Enhance aesthetics
|
550 |
+
fig.patch.set_facecolor('#f8f9fa')
|
551 |
+
ax.set_facecolor('#f0f0f0')
|
552 |
+
|
553 |
+
# Add a tight layout to ensure everything fits
|
554 |
+
fig.tight_layout(rect=[0, 0.05, 1, 0.95])
|
555 |
+
|
556 |
+
# Print debug information
|
557 |
+
print(f"Generated comparison chart for {breed1} vs {breed2}")
|
558 |
+
|
559 |
+
return fig
|
560 |
+
|
561 |
+
except Exception as e:
|
562 |
+
# Create empty figure with error message
|
563 |
+
fig = Figure(figsize=(10, 6))
|
564 |
+
ax = fig.add_subplot(111)
|
565 |
+
ax.text(0.5, 0.5, f"Error generating comparison: {str(e)}",
|
566 |
+
horizontalalignment='center', verticalalignment='center',
|
567 |
+
transform=ax.transAxes, fontsize=14)
|
568 |
+
ax.axis('off')
|
569 |
+
print(f"Error in generate_comparison_chart: {str(e)}")
|
570 |
+
return fig
|