histlearn commited on
Commit
7a4991d
·
verified ·
1 Parent(s): e5adda8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +57 -82
app.py CHANGED
@@ -268,10 +268,16 @@ class ReportGenerator:
268
 
269
  def create_time_performance_plot(self) -> plt.Figure:
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')
277
 
@@ -281,15 +287,15 @@ class ReportGenerator:
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,61 +304,56 @@ class ReportGenerator:
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]
334
  else:
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
349
- label_x = x + base_radius * np.cos(angle)
350
- label_y = y + base_radius * np.sin(angle)
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,7 +369,7 @@ class ReportGenerator:
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
@@ -380,45 +381,32 @@ class ReportGenerator:
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))
392
  plt.legend(
393
  by_label.values(),
394
  by_label.keys(),
395
- bbox_to_anchor=(1.05, 1),
396
  loc='upper left',
397
  borderaxespad=0,
398
  frameon=True,
399
  fancybox=True
400
  )
401
 
402
- plt.tight_layout()
403
  return plt.gcf()
404
 
405
  def create_tasks_performance_plot(self) -> plt.Figure:
406
  """Cria o gráfico de relação entre tarefas e acertos com visualização otimizada."""
407
- # Dimensões do A4 paisagem em polegadas (width=11.69, height=8.27)
408
- # Usando um pouco menos para garantir margens
409
- plt.figure(figsize=(11, 7))
410
-
411
- # Ajustar margens para garantir que tudo fique visível
412
  plt.subplots_adjust(
413
- left=0.08, # Reduzido para dar mais espaço ao gráfico
414
- right=0.88, # Aumentado para aproximar a legenda
415
- top=0.92, # Aumentado para usar mais espaço vertical
416
- bottom=0.12 # Ajustado para equilibrar
417
  )
418
 
419
  ax = plt.gca()
420
-
421
- # Configuração inicial
422
  plt.grid(True, alpha=0.2, linestyle='--')
423
  ax.set_facecolor('#f8f9fa')
424
 
@@ -459,7 +447,6 @@ class ReportGenerator:
459
 
460
  # Processar pontos por nível
461
  for nivel, points in points_by_level.items():
462
- # Agrupar pontos com mesmas coordenadas
463
  point_groups = {}
464
  for t, a, nome in points:
465
  key = (round(t, 1), round(a, 1))
@@ -475,30 +462,18 @@ class ReportGenerator:
475
  label = f"{len(group)} alunos com\n{group[0][0]:.0f} tarefas:\n" + \
476
  "\n".join(sorted(p[2].split()[0] for p in group))
477
 
478
- # Encontrar posição livre para o label
479
- radius = 2.0 + len(group) * 0.5
480
- angle = np.random.uniform(0, 2 * np.pi)
481
- found_position = False
482
 
483
- # Tentar diferentes posições até encontrar uma livre
484
- for r in np.arange(radius, radius * 3, radius/2):
485
- if found_position:
486
- break
487
- for angle_offset in np.linspace(0, 2*np.pi, 16):
488
- test_angle = angle + angle_offset
489
- label_x = x + r * np.cos(test_angle)
490
- label_y = y + r * np.sin(test_angle)
491
-
492
- # Verificar se posição está livre
493
- if not any(calculate_distance((label_x, label_y), pos) < 2.0
494
- for pos in used_positions):
495
- used_positions.append((label_x, label_y))
496
- found_position = True
497
- break
498
-
499
- if not found_position:
500
- label_x = x + radius * np.cos(angle)
501
- label_y = y + radius * np.sin(angle)
502
 
503
  # Criar anotação
504
  plt.annotate(
@@ -516,7 +491,7 @@ class ReportGenerator:
516
  fontsize=9,
517
  arrowprops=dict(
518
  arrowstyle='-|>',
519
- connectionstyle='angle3,angleA=90,angleB=0',
520
  color='gray',
521
  alpha=0.6,
522
  mutation_scale=15
@@ -547,15 +522,15 @@ class ReportGenerator:
547
  plt.legend(
548
  by_label.values(),
549
  by_label.keys(),
550
- bbox_to_anchor=(1.08, 1), # Reduzido para aproximar do gráfico
551
  loc='upper left',
552
- borderaxespad=1,
553
  frameon=True,
554
  fancybox=True
555
  )
556
 
557
  return plt.gcf()
558
-
559
  def generate_graphs(self) -> List[plt.Figure]:
560
  """Gera todos os gráficos para o relatório."""
561
  try:
 
268
 
269
  def create_time_performance_plot(self) -> plt.Figure:
270
  """Cria o gráfico de relação entre tempo e acertos com visualização otimizada."""
271
+ # Configuração inicial com tamanho otimizado para A4 paisagem
272
+ plt.figure(figsize=(13, 7.5))
273
+ plt.subplots_adjust(
274
+ left=0.07, # Margem esquerda reduzida
275
+ right=0.93, # Margem direita aumentada
276
+ top=0.95, # Margem superior aumentada
277
+ bottom=0.1 # Margem inferior reduzida
278
+ )
279
+
280
  ax = plt.gca()
 
 
281
  plt.grid(True, alpha=0.2, linestyle='--')
282
  ax.set_facecolor('#f8f9fa')
283
 
 
287
  # Estruturas para armazenamento
288
  points_by_level = {level: [] for level in self.colors.keys()}
289
  label_positions = {}
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
  plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
305
  label='Média de Acertos')
306
 
307
+ # Processar pontos por nível
308
  for nivel, points in points_by_level.items():
 
309
  points.sort(key=lambda p: (p[1], p[0]))
 
 
310
  groups = []
311
  used = set()
312
+
313
+ # Agrupar pontos próximos
314
  for i, p1 in enumerate(points):
315
  if i in used:
316
  continue
317
 
318
  current_group = [p1]
319
  used.add(i)
320
+
321
  for j, p2 in enumerate(points):
322
  if j not in used and calculate_distance(p1[:2], p2[:2]) < 2.0:
323
  current_group.append(p2)
324
  used.add(j)
325
+
326
  groups.append(current_group)
327
 
328
  # Processar cada grupo
329
  for group in groups:
330
+ # Calcular centroide do grupo
331
  center_x = sum(p[0] for p in group) / len(group)
332
  center_y = sum(p[1] for p in group) / len(group)
333
+
334
  # Determinar texto do label
335
  if len(group) == 1:
336
  x, y, nome = group[0]
337
  label_text = nome.split()[0]
338
  else:
339
  x, y = center_x, center_y
340
+ label_text = "\n".join(sorted(p[2].split()[0] for p in group))
341
 
342
+ # Calcular posição otimizada para o label
343
+ radius = 2.5 + len(group) * 0.5
344
+ base_angle = np.arctan2(y - np.mean(self.data['Acertos Absolutos']),
345
+ x - np.mean(pd.to_timedelta(self.data['Total Tempo']).dt.total_seconds() / 60))
346
+
347
+ # Ajustar ângulo baseado na posição
348
+ if x < np.mean(pd.to_timedelta(self.data['Total Tempo']).dt.total_seconds() / 60):
349
+ angle = base_angle - np.pi/6
350
+ else:
351
+ angle = base_angle + np.pi/6
352
+
353
+ label_x = x + radius * np.cos(angle)
354
+ label_y = y + radius * np.sin(angle)
355
+
356
+ # Criar anotação
 
 
 
357
  plt.annotate(
358
  label_text,
359
  (x, y),
 
369
  fontsize=9,
370
  arrowprops=dict(
371
  arrowstyle='-|>',
372
+ connectionstyle='arc3,rad=-0.2',
373
  color='gray',
374
  alpha=0.6,
375
  mutation_scale=15
 
381
  plt.xlabel('Tempo Total (minutos)', fontsize=12)
382
  plt.ylabel('Número de Acertos', fontsize=12)
383
 
 
 
 
 
 
 
384
  # Legenda
385
  handles, labels = plt.gca().get_legend_handles_labels()
386
  by_label = dict(zip(labels, handles))
387
  plt.legend(
388
  by_label.values(),
389
  by_label.keys(),
390
+ bbox_to_anchor=(1.02, 1),
391
  loc='upper left',
392
  borderaxespad=0,
393
  frameon=True,
394
  fancybox=True
395
  )
396
 
 
397
  return plt.gcf()
398
 
399
  def create_tasks_performance_plot(self) -> plt.Figure:
400
  """Cria o gráfico de relação entre tarefas e acertos com visualização otimizada."""
401
+ plt.figure(figsize=(13, 7.5))
 
 
 
 
402
  plt.subplots_adjust(
403
+ left=0.07,
404
+ right=0.93,
405
+ top=0.95,
406
+ bottom=0.1
407
  )
408
 
409
  ax = plt.gca()
 
 
410
  plt.grid(True, alpha=0.2, linestyle='--')
411
  ax.set_facecolor('#f8f9fa')
412
 
 
447
 
448
  # Processar pontos por nível
449
  for nivel, points in points_by_level.items():
 
450
  point_groups = {}
451
  for t, a, nome in points:
452
  key = (round(t, 1), round(a, 1))
 
462
  label = f"{len(group)} alunos com\n{group[0][0]:.0f} tarefas:\n" + \
463
  "\n".join(sorted(p[2].split()[0] for p in group))
464
 
465
+ # Calcular posição do label
466
+ radius = 2.5 + len(group) * 0.5
467
+ base_angle = np.arctan2(y - np.mean(self.data['Acertos Absolutos']),
468
+ x - np.mean(self.data['Tarefas Completadas']))
469
 
470
+ if x < np.mean(self.data['Tarefas Completadas']):
471
+ angle = base_angle - np.pi/6
472
+ else:
473
+ angle = base_angle + np.pi/6
474
+
475
+ label_x = x + radius * np.cos(angle)
476
+ label_y = y + radius * np.sin(angle)
 
 
 
 
 
 
 
 
 
 
 
 
477
 
478
  # Criar anotação
479
  plt.annotate(
 
491
  fontsize=9,
492
  arrowprops=dict(
493
  arrowstyle='-|>',
494
+ connectionstyle='arc3,rad=-0.2',
495
  color='gray',
496
  alpha=0.6,
497
  mutation_scale=15
 
522
  plt.legend(
523
  by_label.values(),
524
  by_label.keys(),
525
+ bbox_to_anchor=(1.02, 1),
526
  loc='upper left',
527
+ borderaxespad=0,
528
  frameon=True,
529
  fancybox=True
530
  )
531
 
532
  return plt.gcf()
533
+
534
  def generate_graphs(self) -> List[plt.Figure]:
535
  """Gera todos os gráficos para o relatório."""
536
  try: