Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -270,7 +270,7 @@ class ReportGenerator:
|
|
| 270 |
"""Cria o gráfico de relação entre tempo e acertos com visualização otimizada."""
|
| 271 |
plt.figure(figsize=(15, 10))
|
| 272 |
ax = plt.gca()
|
| 273 |
-
|
| 274 |
# Configuração inicial
|
| 275 |
plt.grid(True, alpha=0.2, linestyle='--')
|
| 276 |
ax.set_facecolor('#f8f9fa')
|
|
@@ -278,24 +278,18 @@ class ReportGenerator:
|
|
| 278 |
def calculate_distance(p1, p2):
|
| 279 |
return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
| 280 |
|
| 281 |
-
def create_bezier_curve(start, end):
|
| 282 |
-
mid_x = (start[0] + end[0]) / 2
|
| 283 |
-
return f'M{start[0]},{start[1]} ' \
|
| 284 |
-
f'C{mid_x},{start[1]} {mid_x},{end[1]} {end[0]},{end[1]}'
|
| 285 |
-
|
| 286 |
# Estruturas para armazenamento
|
| 287 |
points_by_level = {level: [] for level in self.colors.keys()}
|
| 288 |
label_positions = {}
|
| 289 |
-
connection_lines = []
|
| 290 |
|
| 291 |
# Coletar pontos por nível
|
| 292 |
for nivel, color in self.colors.items():
|
| 293 |
mask = self.data['Nível'] == nivel
|
| 294 |
tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
|
| 295 |
acertos = self.data[mask]['Acertos Absolutos']
|
| 296 |
-
|
| 297 |
plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=100)
|
| 298 |
-
|
| 299 |
for t, a, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
|
| 300 |
points_by_level[nivel].append((t, a, nome))
|
| 301 |
|
|
@@ -304,36 +298,36 @@ class ReportGenerator:
|
|
| 304 |
plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
|
| 305 |
label='Média de Acertos')
|
| 306 |
|
| 307 |
-
# Processamento por nível
|
| 308 |
for nivel, points in points_by_level.items():
|
| 309 |
-
# Ordenar pontos por Y
|
| 310 |
points.sort(key=lambda p: (p[1], p[0]))
|
| 311 |
-
|
| 312 |
# Agrupar pontos próximos
|
| 313 |
groups = []
|
| 314 |
used = set()
|
| 315 |
-
|
| 316 |
for i, p1 in enumerate(points):
|
| 317 |
if i in used:
|
| 318 |
continue
|
| 319 |
-
|
| 320 |
current_group = [p1]
|
| 321 |
used.add(i)
|
| 322 |
-
|
| 323 |
for j, p2 in enumerate(points):
|
| 324 |
if j not in used and calculate_distance(p1[:2], p2[:2]) < 2.0:
|
| 325 |
current_group.append(p2)
|
| 326 |
used.add(j)
|
| 327 |
-
|
| 328 |
groups.append(current_group)
|
| 329 |
|
| 330 |
# Processar cada grupo
|
| 331 |
for group in groups:
|
| 332 |
-
# Calcular centroide
|
| 333 |
center_x = sum(p[0] for p in group) / len(group)
|
| 334 |
center_y = sum(p[1] for p in group) / len(group)
|
| 335 |
-
|
| 336 |
-
# Determinar
|
| 337 |
if len(group) == 1:
|
| 338 |
x, y, nome = group[0]
|
| 339 |
label_text = nome.split()[0]
|
|
@@ -341,14 +335,14 @@ class ReportGenerator:
|
|
| 341 |
x, y = center_x, center_y
|
| 342 |
label_text = '\n'.join(sorted(p[2].split()[0] for p in group))
|
| 343 |
|
| 344 |
-
# Calcular posição
|
| 345 |
angle = np.random.uniform(0, 2*np.pi)
|
| 346 |
base_radius = 3.0 + len(group) * 0.5
|
| 347 |
-
|
| 348 |
label_x = x + base_radius * np.cos(angle)
|
| 349 |
label_y = y + base_radius * np.sin(angle)
|
| 350 |
-
|
| 351 |
-
# Ajustar posição se
|
| 352 |
while any(calculate_distance((label_x, label_y), pos) < 2.0
|
| 353 |
for pos in label_positions.values()):
|
| 354 |
angle += np.pi/4
|
|
@@ -357,8 +351,8 @@ class ReportGenerator:
|
|
| 357 |
|
| 358 |
# Guardar posição do label
|
| 359 |
label_positions[(x, y)] = (label_x, label_y)
|
| 360 |
-
|
| 361 |
-
# Criar anotação com
|
| 362 |
plt.annotate(
|
| 363 |
label_text,
|
| 364 |
(x, y),
|
|
@@ -374,27 +368,24 @@ class ReportGenerator:
|
|
| 374 |
fontsize=9,
|
| 375 |
arrowprops=dict(
|
| 376 |
arrowstyle='-|>',
|
| 377 |
-
connectionstyle=
|
| 378 |
color='gray',
|
| 379 |
alpha=0.6,
|
| 380 |
mutation_scale=15
|
| 381 |
)
|
| 382 |
)
|
| 383 |
-
|
| 384 |
-
# Registrar linha de conexão
|
| 385 |
-
connection_lines.append(((x, y), (label_x, label_y)))
|
| 386 |
|
| 387 |
# Configurações finais
|
| 388 |
plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
|
| 389 |
plt.xlabel('Tempo Total (minutos)', fontsize=12)
|
| 390 |
plt.ylabel('Número de Acertos', fontsize=12)
|
| 391 |
-
|
| 392 |
-
# Ajustar limites
|
| 393 |
x_min, x_max = plt.xlim()
|
| 394 |
y_min, y_max = plt.ylim()
|
| 395 |
plt.xlim(x_min - 1, x_max + 1)
|
| 396 |
plt.ylim(max(0, y_min - 1), y_max + 1)
|
| 397 |
-
|
| 398 |
# Legenda
|
| 399 |
handles, labels = plt.gca().get_legend_handles_labels()
|
| 400 |
by_label = dict(zip(labels, handles))
|
|
@@ -407,7 +398,7 @@ class ReportGenerator:
|
|
| 407 |
frameon=True,
|
| 408 |
fancybox=True
|
| 409 |
)
|
| 410 |
-
|
| 411 |
plt.tight_layout()
|
| 412 |
return plt.gcf()
|
| 413 |
|
|
|
|
| 270 |
"""Cria o gráfico de relação entre tempo e acertos com visualização otimizada."""
|
| 271 |
plt.figure(figsize=(15, 10))
|
| 272 |
ax = plt.gca()
|
| 273 |
+
|
| 274 |
# Configuração inicial
|
| 275 |
plt.grid(True, alpha=0.2, linestyle='--')
|
| 276 |
ax.set_facecolor('#f8f9fa')
|
|
|
|
| 278 |
def calculate_distance(p1, p2):
|
| 279 |
return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
| 280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
# Estruturas para armazenamento
|
| 282 |
points_by_level = {level: [] for level in self.colors.keys()}
|
| 283 |
label_positions = {}
|
|
|
|
| 284 |
|
| 285 |
# Coletar pontos por nível
|
| 286 |
for nivel, color in self.colors.items():
|
| 287 |
mask = self.data['Nível'] == nivel
|
| 288 |
tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
|
| 289 |
acertos = self.data[mask]['Acertos Absolutos']
|
| 290 |
+
|
| 291 |
plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=100)
|
| 292 |
+
|
| 293 |
for t, a, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
|
| 294 |
points_by_level[nivel].append((t, a, nome))
|
| 295 |
|
|
|
|
| 298 |
plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
|
| 299 |
label='Média de Acertos')
|
| 300 |
|
| 301 |
+
# Processamento por nível
|
| 302 |
for nivel, points in points_by_level.items():
|
| 303 |
+
# Ordenar pontos por Y
|
| 304 |
points.sort(key=lambda p: (p[1], p[0]))
|
| 305 |
+
|
| 306 |
# Agrupar pontos próximos
|
| 307 |
groups = []
|
| 308 |
used = set()
|
| 309 |
+
|
| 310 |
for i, p1 in enumerate(points):
|
| 311 |
if i in used:
|
| 312 |
continue
|
| 313 |
+
|
| 314 |
current_group = [p1]
|
| 315 |
used.add(i)
|
| 316 |
+
|
| 317 |
for j, p2 in enumerate(points):
|
| 318 |
if j not in used and calculate_distance(p1[:2], p2[:2]) < 2.0:
|
| 319 |
current_group.append(p2)
|
| 320 |
used.add(j)
|
| 321 |
+
|
| 322 |
groups.append(current_group)
|
| 323 |
|
| 324 |
# Processar cada grupo
|
| 325 |
for group in groups:
|
| 326 |
+
# Calcular centroide
|
| 327 |
center_x = sum(p[0] for p in group) / len(group)
|
| 328 |
center_y = sum(p[1] for p in group) / len(group)
|
| 329 |
+
|
| 330 |
+
# Determinar texto do label
|
| 331 |
if len(group) == 1:
|
| 332 |
x, y, nome = group[0]
|
| 333 |
label_text = nome.split()[0]
|
|
|
|
| 335 |
x, y = center_x, center_y
|
| 336 |
label_text = '\n'.join(sorted(p[2].split()[0] for p in group))
|
| 337 |
|
| 338 |
+
# Calcular posição do label
|
| 339 |
angle = np.random.uniform(0, 2*np.pi)
|
| 340 |
base_radius = 3.0 + len(group) * 0.5
|
| 341 |
+
|
| 342 |
label_x = x + base_radius * np.cos(angle)
|
| 343 |
label_y = y + base_radius * np.sin(angle)
|
| 344 |
+
|
| 345 |
+
# Ajustar posição se próxima de outros labels
|
| 346 |
while any(calculate_distance((label_x, label_y), pos) < 2.0
|
| 347 |
for pos in label_positions.values()):
|
| 348 |
angle += np.pi/4
|
|
|
|
| 351 |
|
| 352 |
# Guardar posição do label
|
| 353 |
label_positions[(x, y)] = (label_x, label_y)
|
| 354 |
+
|
| 355 |
+
# Criar anotação com estilo de conexão padrão
|
| 356 |
plt.annotate(
|
| 357 |
label_text,
|
| 358 |
(x, y),
|
|
|
|
| 368 |
fontsize=9,
|
| 369 |
arrowprops=dict(
|
| 370 |
arrowstyle='-|>',
|
| 371 |
+
connectionstyle='arc3,rad=0.2', # Usando estilo de conexão padrão
|
| 372 |
color='gray',
|
| 373 |
alpha=0.6,
|
| 374 |
mutation_scale=15
|
| 375 |
)
|
| 376 |
)
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
# Configurações finais
|
| 379 |
plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
|
| 380 |
plt.xlabel('Tempo Total (minutos)', fontsize=12)
|
| 381 |
plt.ylabel('Número de Acertos', fontsize=12)
|
| 382 |
+
|
| 383 |
+
# Ajustar limites
|
| 384 |
x_min, x_max = plt.xlim()
|
| 385 |
y_min, y_max = plt.ylim()
|
| 386 |
plt.xlim(x_min - 1, x_max + 1)
|
| 387 |
plt.ylim(max(0, y_min - 1), y_max + 1)
|
| 388 |
+
|
| 389 |
# Legenda
|
| 390 |
handles, labels = plt.gca().get_legend_handles_labels()
|
| 391 |
by_label = dict(zip(labels, handles))
|
|
|
|
| 398 |
frameon=True,
|
| 399 |
fancybox=True
|
| 400 |
)
|
| 401 |
+
|
| 402 |
plt.tight_layout()
|
| 403 |
return plt.gcf()
|
| 404 |
|