Correction des multiples bugs
This commit is contained in:
parent
5fe70d754b
commit
07198c20b7
15 changed files with 707 additions and 348 deletions
|
|
@ -9,6 +9,11 @@ from collections import defaultdict
|
|||
from typing import List, Dict, Any, Optional
|
||||
|
||||
import discord
|
||||
import matplotlib.pyplot as plt
|
||||
import plotly.graph_objects as go
|
||||
import plotly.express as px
|
||||
from plotly.subplots import make_subplots
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from .data.storage import get_storage
|
||||
|
|
@ -102,213 +107,74 @@ class StaffAnalytics:
|
|||
return result
|
||||
|
||||
def create_staff_rating_chart(self, staff_list: List[Dict[str, Any]]) -> io.BytesIO:
|
||||
"""Create a comprehensive staff rating chart"""
|
||||
"""Create a comprehensive staff rating chart using matplotlib"""
|
||||
# Filter staff with ratings
|
||||
rated_staff = [s for s in staff_list if s['total_ratings'] > 0]
|
||||
|
||||
if not rated_staff:
|
||||
# Return empty chart
|
||||
img = Image.new('RGB', (800, 400), '#2F3136')
|
||||
fig, ax = plt.subplots(figsize=(10, 6), facecolor='#2F3136')
|
||||
ax.set_facecolor('#2F3136')
|
||||
ax.text(0.5, 0.5, 'Aucune évaluation disponible', ha='center', va='center',
|
||||
transform=ax.transAxes, color='white')
|
||||
ax.set_title('Évaluations des Membres du Staff', color='white')
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format='PNG')
|
||||
plt.savefig(buf, format='png', bbox_inches='tight', facecolor='#2F3136')
|
||||
buf.seek(0)
|
||||
plt.close()
|
||||
return buf
|
||||
|
||||
# Image dimensions
|
||||
width, height = 1200, 700
|
||||
img = Image.new('RGB', (width, height), '#36393F') # Discord dark theme
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Try to load fonts with better Unicode support
|
||||
try:
|
||||
# Try different font paths for better Unicode support
|
||||
font_paths = [
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
||||
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
||||
"/System/Library/Fonts/Arial.ttf",
|
||||
"arial.ttf"
|
||||
]
|
||||
font_title = None
|
||||
font_label = None
|
||||
font_value = None
|
||||
|
||||
for font_path in font_paths:
|
||||
try:
|
||||
font_title = ImageFont.truetype(font_path, 24)
|
||||
font_label = ImageFont.truetype(font_path, 16)
|
||||
font_value = ImageFont.truetype(font_path, 14)
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not font_title:
|
||||
raise Exception("No suitable font found")
|
||||
|
||||
except:
|
||||
# Fallback to default with better handling
|
||||
font_title = ImageFont.load_default()
|
||||
font_label = ImageFont.load_default()
|
||||
font_value = ImageFont.load_default()
|
||||
|
||||
# Title with better styling
|
||||
title = "Évaluations des Membres du Staff"
|
||||
# Add background for title
|
||||
title_bbox = draw.textbbox((0, 0), title, font=font_title)
|
||||
title_width = title_bbox[2] - title_bbox[0]
|
||||
title_x = (width - title_width) // 2
|
||||
draw.rectangle([title_x - 20, 15, title_x + title_width + 20, 55], fill='#5865F2', outline='#FFFFFF', width=2)
|
||||
draw.text((width//2, 35), title, fill='white', font=font_title, anchor='mm')
|
||||
|
||||
# Chart dimensions with better spacing
|
||||
chart_left = 200
|
||||
chart_right = width - 100
|
||||
chart_top = 100
|
||||
chart_bottom = height - 200
|
||||
chart_width = chart_right - chart_left
|
||||
chart_height = chart_bottom - chart_top
|
||||
|
||||
# Prepare data - limit to top 8 for better readability
|
||||
top_staff = sorted(rated_staff, key=lambda x: x['average_rating'], reverse=True)[:8]
|
||||
names = []
|
||||
for s in top_staff:
|
||||
name = s['name']
|
||||
# Handle Unicode properly
|
||||
try:
|
||||
# Ensure proper encoding
|
||||
name = name.encode('utf-8').decode('utf-8')
|
||||
except:
|
||||
name = str(name)
|
||||
# Truncate long names
|
||||
if len(name) > 12:
|
||||
name = name[:12] + '...'
|
||||
names.append(name)
|
||||
|
||||
# Sort by average rating
|
||||
rated_staff.sort(key=lambda x: x['average_rating'], reverse=True)
|
||||
|
||||
# Prepare data - limit to top 10 for better readability
|
||||
top_staff = rated_staff[:10]
|
||||
names = [s['name'][:15] + '...' if len(s['name']) > 15 else s['name'] for s in top_staff]
|
||||
ratings = [s['average_rating'] for s in top_staff]
|
||||
counts = [s['total_ratings'] for s in top_staff]
|
||||
|
||||
if not ratings:
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format='PNG')
|
||||
buf.seek(0)
|
||||
return buf
|
||||
|
||||
max_rating = 5
|
||||
bar_width = min(80, chart_width // len(ratings)) if ratings else 1
|
||||
spacing = 20
|
||||
|
||||
# Draw background grid
|
||||
for i in range(6):
|
||||
y = chart_bottom - (i * chart_height // 5)
|
||||
draw.line([chart_left, y, chart_right, y], fill='#72767D', width=1)
|
||||
# Rating labels
|
||||
draw.text((chart_left - 40, y), f"{i}", fill='#FFFFFF', font=font_value, anchor='mm')
|
||||
|
||||
# Draw bars with gradient effect
|
||||
for i, (name, rating, count) in enumerate(zip(names, ratings, counts)):
|
||||
bar_height = int((rating / max_rating) * chart_height)
|
||||
|
||||
x1 = chart_left + i * (bar_width + spacing) + spacing//2
|
||||
y1 = chart_bottom - bar_height
|
||||
x2 = x1 + bar_width
|
||||
y2 = chart_bottom
|
||||
|
||||
# Bar color based on rating with better colors
|
||||
if rating >= 4.5:
|
||||
color = '#57F287' # Green
|
||||
shadow_color = '#4CAF50'
|
||||
elif rating >= 3.5:
|
||||
color = '#FEE75C' # Yellow
|
||||
shadow_color = '#FFC107'
|
||||
elif rating >= 2.5:
|
||||
color = '#FAA61A' # Orange
|
||||
shadow_color = '#FF9800'
|
||||
else:
|
||||
color = '#ED4245' # Red
|
||||
shadow_color = '#F44336'
|
||||
|
||||
# Draw shadow for 3D effect
|
||||
draw.rectangle([x1+2, y1+2, x2+2, y2], fill=shadow_color, outline=shadow_color)
|
||||
|
||||
# Main bar
|
||||
draw.rectangle([x1, y1, x2, y2], fill=color, outline='#FFFFFF', width=2)
|
||||
|
||||
# Rating value on top with background
|
||||
rating_text = f"{rating:.1f}⭐"
|
||||
text_bbox = draw.textbbox((0, 0), rating_text, font=font_value)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_x = x1 + bar_width//2
|
||||
draw.rectangle([text_x - text_width//2 - 5, y1 - 25, text_x + text_width//2 + 5, y1 - 5], fill='#36393F', outline='#FFFFFF', width=1)
|
||||
draw.text((text_x, y1 - 15), rating_text, fill='white', font=font_value, anchor='mm')
|
||||
|
||||
# Count below with background
|
||||
count_text = f"({count})"
|
||||
count_bbox = draw.textbbox((0, 0), count_text, font=font_value)
|
||||
count_width = count_bbox[2] - count_bbox[0]
|
||||
draw.rectangle([text_x - count_width//2 - 5, y2 + 5, text_x + count_width//2 + 5, y2 + 25], fill='#36393F', outline='#FFFFFF', width=1)
|
||||
draw.text((text_x, y2 + 15), count_text, fill='white', font=font_value, anchor='mm')
|
||||
|
||||
# Draw staff names with better positioning
|
||||
for i, name in enumerate(names):
|
||||
x = chart_left + i * (bar_width + spacing) + bar_width//2 + spacing//2
|
||||
|
||||
# Handle long names by splitting
|
||||
lines = []
|
||||
if len(name) > 8:
|
||||
# Split at spaces if possible
|
||||
words = name.split()
|
||||
current_line = ""
|
||||
for word in words:
|
||||
if len(current_line + word) < 8:
|
||||
current_line += word + " "
|
||||
else:
|
||||
lines.append(current_line.strip())
|
||||
current_line = word + " "
|
||||
if current_line:
|
||||
lines.append(current_line.strip())
|
||||
if not lines:
|
||||
lines = [name[:8] + '...']
|
||||
else:
|
||||
lines = [name]
|
||||
|
||||
y_offset = chart_bottom + 40
|
||||
for line in lines:
|
||||
# Background for name
|
||||
name_bbox = draw.textbbox((0, 0), line, font=font_label)
|
||||
name_width = name_bbox[2] - name_bbox[0]
|
||||
draw.rectangle([x - name_width//2 - 5, y_offset - 5, x + name_width//2 + 5, y_offset + 15], fill='#36393F', outline='#FFFFFF', width=1)
|
||||
draw.text((x, y_offset + 5), line, fill='white', font=font_label, anchor='mm')
|
||||
y_offset += 20
|
||||
|
||||
# Y-axis label
|
||||
draw.text((chart_left - 120, height//2), 'Note Moyenne', fill='white', font=font_label, anchor='mm')
|
||||
|
||||
# Enhanced legend with better positioning
|
||||
legend_x = chart_right - 250
|
||||
legend_y = chart_top + 50
|
||||
|
||||
# Legend background
|
||||
draw.rectangle([legend_x - 20, legend_y - 10, legend_x + 230, legend_y + 120], fill='#36393F', outline='#FFFFFF', width=2)
|
||||
|
||||
draw.text((legend_x, legend_y), "Légende des Notes:", fill='white', font=font_label)
|
||||
|
||||
legend_items = [
|
||||
("🟢 Excellent", "4.5+", '#57F287'),
|
||||
("🟡 Bon", "3.5-4.4", '#FEE75C'),
|
||||
("🟠 Moyen", "2.5-3.4", '#FAA61A'),
|
||||
("🔴 À améliorer", "<2.5", '#ED4245')
|
||||
# Create figure
|
||||
fig, ax = plt.subplots(figsize=(12, 8), facecolor='#2F3136')
|
||||
fig.patch.set_facecolor('#2F3136')
|
||||
ax.set_facecolor('#2F3136')
|
||||
|
||||
# Create bars with color coding
|
||||
colors = ['#57F287' if r >= 4.5 else '#FEE75C' if r >= 3.5 else '#FAA61A' if r >= 2.5 else '#ED4245' for r in ratings]
|
||||
bars = ax.bar(range(len(ratings)), ratings, color=colors)
|
||||
|
||||
# Customize
|
||||
ax.set_xticks(range(len(names)))
|
||||
ax.set_xticklabels(names, rotation=45, ha='right', color='white')
|
||||
ax.set_ylabel('Note moyenne', color='white')
|
||||
ax.set_title('Évaluations des Membres du Staff', color='white', fontsize=16)
|
||||
ax.tick_params(colors='white')
|
||||
ax.grid(True, alpha=0.3, color='gray')
|
||||
|
||||
# Add value labels on bars (avoiding Unicode stars to prevent warnings)
|
||||
for i, (bar, rating, count) in enumerate(zip(bars, ratings, counts)):
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05,
|
||||
f'{rating:.1f}\n({count})', ha='center', va='bottom', color='white', fontsize=8)
|
||||
|
||||
# Set y-axis limits
|
||||
ax.set_ylim(0, 5.2)
|
||||
|
||||
# Add legend
|
||||
legend_elements = [
|
||||
plt.Rectangle((0,0),1,1, color='#57F287', label='Excellent (4.5+)'),
|
||||
plt.Rectangle((0,0),1,1, color='#FEE75C', label='Bon (3.5-4.4)'),
|
||||
plt.Rectangle((0,0),1,1, color='#FAA61A', label='Moyen (2.5-3.4)'),
|
||||
plt.Rectangle((0,0),1,1, color='#ED4245', label='À améliorer (<2.5)')
|
||||
]
|
||||
|
||||
for i, (emoji_text, range_text, color) in enumerate(legend_items):
|
||||
y_pos = legend_y + 25 + i * 20
|
||||
draw.text((legend_x, y_pos), f"{emoji_text} {range_text}", fill=color, font=font_value)
|
||||
|
||||
# Add some stats at the bottom
|
||||
stats_text = f"Total évalué: {len(rated_staff)} membres • Moyenne générale: {sum(ratings)/len(ratings):.1f}⭐"
|
||||
draw.text((width//2, height - 30), stats_text, fill='#B9BBBE', font=font_value, anchor='mm')
|
||||
|
||||
ax.legend(handles=legend_elements, loc='upper right', facecolor='#2F3136', edgecolor='white', labelcolor='white')
|
||||
|
||||
# Adjust layout
|
||||
plt.tight_layout()
|
||||
|
||||
# Save to buffer
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format='PNG')
|
||||
plt.savefig(buf, format='png', bbox_inches='tight', facecolor='#2F3136')
|
||||
buf.seek(0)
|
||||
plt.close()
|
||||
return buf
|
||||
|
||||
def create_individual_staff_chart(self, staff_data: Dict[str, Any]) -> io.BytesIO:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue