DawnC commited on
Commit
111f3b5
·
verified ·
1 Parent(s): 7b8cadb

Upload breed_visualization.py

Browse files
Files changed (1) hide show
  1. 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