Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -276,45 +276,95 @@ class ReportGenerator:
|
|
276 |
plt.grid(True, alpha=0.2, linestyle='--')
|
277 |
plt.gca().set_facecolor('#f8f9fa')
|
278 |
|
279 |
-
|
|
|
280 |
|
281 |
-
#
|
282 |
for nivel, color in self.colors.items():
|
283 |
mask = self.data['Nível'] == nivel
|
284 |
tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
|
285 |
acertos = self.data[mask]['Acertos Absolutos']
|
286 |
|
287 |
-
|
288 |
-
plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=150)
|
289 |
|
290 |
-
#
|
291 |
for x, y, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
plt.title('Relação entre Tempo e Acertos por Nível', pad=20)
|
304 |
plt.xlabel('Tempo Total (minutos)')
|
305 |
plt.ylabel('Número de Acertos')
|
306 |
|
307 |
# Melhorar posição e aparência da legenda
|
308 |
-
plt.legend(
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
|
|
|
|
314 |
|
315 |
-
# Ajustar margens para acomodar a legenda
|
316 |
plt.tight_layout()
|
317 |
-
|
318 |
return plt.gcf()
|
319 |
|
320 |
def create_tasks_performance_plot(self) -> plt.Figure:
|
@@ -325,55 +375,106 @@ class ReportGenerator:
|
|
325 |
plt.grid(True, alpha=0.2, linestyle='--')
|
326 |
plt.gca().set_facecolor('#f8f9fa')
|
327 |
|
328 |
-
|
|
|
329 |
|
330 |
-
#
|
331 |
for nivel, color in self.colors.items():
|
332 |
mask = self.data['Nível'] == nivel
|
333 |
tarefas = self.data[mask]['Tarefas Completadas']
|
334 |
acertos = self.data[mask]['Acertos Absolutos']
|
335 |
|
336 |
-
# Criar scatter plot
|
337 |
plt.scatter(tarefas, acertos, c=color, label=nivel, alpha=0.7, s=150)
|
338 |
|
339 |
-
#
|
340 |
for x, y, nome in zip(tarefas, acertos, self.data[mask]['Nome do Aluno']):
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
|
|
|
|
345 |
z = np.polyfit(self.data['Tarefas Completadas'],
|
346 |
self.data['Acertos Absolutos'], 1)
|
347 |
p = np.poly1d(z)
|
348 |
-
x_range = np.linspace(
|
349 |
-
|
350 |
-
|
|
|
|
|
351 |
plt.plot(x_range, p(x_range), "--", color='#e74c3c', alpha=0.8,
|
352 |
-
|
353 |
-
|
354 |
-
#
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
362 |
plt.title('Relação entre Tarefas Completadas e Acertos', pad=20)
|
363 |
plt.xlabel('Número de Tarefas Completadas')
|
364 |
plt.ylabel('Número de Acertos')
|
365 |
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
|
|
373 |
|
374 |
-
# Ajustar margens para acomodar a legenda
|
375 |
plt.tight_layout()
|
376 |
-
|
377 |
return plt.gcf()
|
378 |
|
379 |
def generate_graphs(self) -> List[plt.Figure]:
|
|
|
276 |
plt.grid(True, alpha=0.2, linestyle='--')
|
277 |
plt.gca().set_facecolor('#f8f9fa')
|
278 |
|
279 |
+
# Criar dicionário para armazenar pontos próximos
|
280 |
+
proximity_groups = {}
|
281 |
|
282 |
+
# Primeiro, plotar os pontos
|
283 |
for nivel, color in self.colors.items():
|
284 |
mask = self.data['Nível'] == nivel
|
285 |
tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
|
286 |
acertos = self.data[mask]['Acertos Absolutos']
|
287 |
|
288 |
+
scatter = plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=150)
|
|
|
289 |
|
290 |
+
# Agrupar pontos próximos
|
291 |
for x, y, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
|
292 |
+
key = (round(x, 1), round(y, 1)) # Arredondar para agrupar pontos próximos
|
293 |
+
if key not in proximity_groups:
|
294 |
+
proximity_groups[key] = []
|
295 |
+
proximity_groups[key].append((x, y, nome))
|
296 |
+
|
297 |
+
# Adicionar anotações com tratamento especial para pontos próximos
|
298 |
+
for group in proximity_groups.values():
|
299 |
+
if len(group) == 1:
|
300 |
+
# Ponto único - anotação normal
|
301 |
+
x, y, nome = group[0]
|
302 |
+
plt.annotate(
|
303 |
+
nome.split()[0], # Usar apenas primeiro nome
|
304 |
+
(x, y),
|
305 |
+
xytext=(5, 5),
|
306 |
+
textcoords='offset points',
|
307 |
+
bbox=dict(
|
308 |
+
facecolor='white',
|
309 |
+
edgecolor='none',
|
310 |
+
alpha=0.8,
|
311 |
+
pad=0.5
|
312 |
+
),
|
313 |
+
fontsize=8,
|
314 |
+
arrowprops=dict(
|
315 |
+
arrowstyle='-',
|
316 |
+
color='gray',
|
317 |
+
alpha=0.3,
|
318 |
+
connectionstyle='arc3,rad=0.3'
|
319 |
+
)
|
320 |
+
)
|
321 |
+
else:
|
322 |
+
# Múltiplos pontos próximos - criar lista vertical
|
323 |
+
x_base = sum(p[0] for p in group) / len(group)
|
324 |
+
y_base = sum(p[1] for p in group) / len(group)
|
325 |
+
nomes = [p[2].split()[0] for p in group]
|
326 |
+
|
327 |
+
# Calcular posição do texto para evitar sobreposição
|
328 |
+
x_text = x_base + 10
|
329 |
+
y_text = y_base
|
330 |
+
|
331 |
+
# Criar caixa com lista de nomes
|
332 |
+
nome_text = '\n'.join(nomes)
|
333 |
+
plt.annotate(
|
334 |
+
nome_text,
|
335 |
+
(x_base, y_base),
|
336 |
+
xytext=(x_text, y_text),
|
337 |
+
bbox=dict(
|
338 |
+
facecolor='white',
|
339 |
+
edgecolor='lightgray',
|
340 |
+
alpha=0.9,
|
341 |
+
pad=0.5,
|
342 |
+
boxstyle='round,pad=0.5'
|
343 |
+
),
|
344 |
+
fontsize=8,
|
345 |
+
arrowprops=dict(
|
346 |
+
arrowstyle='->',
|
347 |
+
color='gray',
|
348 |
+
alpha=0.5,
|
349 |
+
connectionstyle='arc3,rad=0.2'
|
350 |
+
)
|
351 |
+
)
|
352 |
+
|
353 |
plt.title('Relação entre Tempo e Acertos por Nível', pad=20)
|
354 |
plt.xlabel('Tempo Total (minutos)')
|
355 |
plt.ylabel('Número de Acertos')
|
356 |
|
357 |
# Melhorar posição e aparência da legenda
|
358 |
+
plt.legend(
|
359 |
+
bbox_to_anchor=(1.05, 1),
|
360 |
+
loc='upper left',
|
361 |
+
borderaxespad=0,
|
362 |
+
frameon=True,
|
363 |
+
facecolor='white',
|
364 |
+
edgecolor='none'
|
365 |
+
)
|
366 |
|
|
|
367 |
plt.tight_layout()
|
|
|
368 |
return plt.gcf()
|
369 |
|
370 |
def create_tasks_performance_plot(self) -> plt.Figure:
|
|
|
375 |
plt.grid(True, alpha=0.2, linestyle='--')
|
376 |
plt.gca().set_facecolor('#f8f9fa')
|
377 |
|
378 |
+
# Dicionário para armazenar pontos próximos
|
379 |
+
proximity_groups = {}
|
380 |
|
381 |
+
# Primeiro, plotar os pontos e a linha de tendência
|
382 |
for nivel, color in self.colors.items():
|
383 |
mask = self.data['Nível'] == nivel
|
384 |
tarefas = self.data[mask]['Tarefas Completadas']
|
385 |
acertos = self.data[mask]['Acertos Absolutos']
|
386 |
|
|
|
387 |
plt.scatter(tarefas, acertos, c=color, label=nivel, alpha=0.7, s=150)
|
388 |
|
389 |
+
# Agrupar pontos próximos
|
390 |
for x, y, nome in zip(tarefas, acertos, self.data[mask]['Nome do Aluno']):
|
391 |
+
key = (x, y) # Usar valores exatos pois tarefas são discretas
|
392 |
+
if key not in proximity_groups:
|
393 |
+
proximity_groups[key] = []
|
394 |
+
proximity_groups[key].append((x, y, nome))
|
395 |
+
|
396 |
+
# Adicionar linha de tendência
|
397 |
z = np.polyfit(self.data['Tarefas Completadas'],
|
398 |
self.data['Acertos Absolutos'], 1)
|
399 |
p = np.poly1d(z)
|
400 |
+
x_range = np.linspace(
|
401 |
+
self.data['Tarefas Completadas'].min(),
|
402 |
+
self.data['Tarefas Completadas'].max(),
|
403 |
+
100
|
404 |
+
)
|
405 |
plt.plot(x_range, p(x_range), "--", color='#e74c3c', alpha=0.8,
|
406 |
+
label='Tendência', linewidth=2)
|
407 |
+
|
408 |
+
# Adicionar anotações com tratamento especial para pontos próximos
|
409 |
+
for group in proximity_groups.values():
|
410 |
+
if len(group) == 1:
|
411 |
+
# Ponto único - anotação normal
|
412 |
+
x, y, nome = group[0]
|
413 |
+
plt.annotate(
|
414 |
+
nome.split()[0], # Usar apenas primeiro nome
|
415 |
+
(x, y),
|
416 |
+
xytext=(5, 5),
|
417 |
+
textcoords='offset points',
|
418 |
+
bbox=dict(
|
419 |
+
facecolor='white',
|
420 |
+
edgecolor='none',
|
421 |
+
alpha=0.8,
|
422 |
+
pad=0.5
|
423 |
+
),
|
424 |
+
fontsize=8,
|
425 |
+
arrowprops=dict(
|
426 |
+
arrowstyle='-',
|
427 |
+
color='gray',
|
428 |
+
alpha=0.3,
|
429 |
+
connectionstyle='arc3,rad=0.3'
|
430 |
+
)
|
431 |
+
)
|
432 |
+
else:
|
433 |
+
# Múltiplos pontos próximos - criar caixa com lista
|
434 |
+
x_base = sum(p[0] for p in group) / len(group)
|
435 |
+
y_base = sum(p[1] for p in group) / len(group)
|
436 |
+
nomes = [p[2].split()[0] for p in group]
|
437 |
+
|
438 |
+
# Determinar posição do texto baseado no quadrante do gráfico
|
439 |
+
x_text = x_base + (0.5 if x_base < np.mean(self.data['Tarefas Completadas']) else -2)
|
440 |
+
y_text = y_base + (0.5 if y_base < np.mean(self.data['Acertos Absolutos']) else -2)
|
441 |
+
|
442 |
+
# Criar caixa com lista de nomes
|
443 |
+
nome_text = '\n'.join(nomes)
|
444 |
+
plt.annotate(
|
445 |
+
nome_text,
|
446 |
+
(x_base, y_base),
|
447 |
+
xytext=(x_text, y_text),
|
448 |
+
bbox=dict(
|
449 |
+
facecolor='white',
|
450 |
+
edgecolor='lightgray',
|
451 |
+
alpha=0.9,
|
452 |
+
pad=0.5,
|
453 |
+
boxstyle='round,pad=0.5'
|
454 |
+
),
|
455 |
+
fontsize=8,
|
456 |
+
arrowprops=dict(
|
457 |
+
arrowstyle='->',
|
458 |
+
color='gray',
|
459 |
+
alpha=0.5,
|
460 |
+
connectionstyle='arc3,rad=0.2'
|
461 |
+
)
|
462 |
+
)
|
463 |
+
|
464 |
plt.title('Relação entre Tarefas Completadas e Acertos', pad=20)
|
465 |
plt.xlabel('Número de Tarefas Completadas')
|
466 |
plt.ylabel('Número de Acertos')
|
467 |
|
468 |
+
plt.legend(
|
469 |
+
bbox_to_anchor=(1.05, 1),
|
470 |
+
loc='upper left',
|
471 |
+
borderaxespad=0,
|
472 |
+
frameon=True,
|
473 |
+
facecolor='white',
|
474 |
+
edgecolor='none'
|
475 |
+
)
|
476 |
|
|
|
477 |
plt.tight_layout()
|
|
|
478 |
return plt.gcf()
|
479 |
|
480 |
def generate_graphs(self) -> List[plt.Figure]:
|