histlearn commited on
Commit
0b5d3e0
·
verified ·
1 Parent(s): bdb4ed2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -160
app.py CHANGED
@@ -268,96 +268,106 @@ 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
- # 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.92, # Margem superior aumentada
277
- bottom=0.12 # Margem inferior reduzida
278
- )
279
-
280
  ax = plt.gca()
 
 
281
  plt.grid(True, alpha=0.2, linestyle='--')
282
  ax.set_facecolor('#f8f9fa')
283
-
284
- def calculate_distance(p1, p2):
285
- return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
 
 
286
 
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
-
302
- # Linha de média
303
- media_acertos = self.data['Acertos Absolutos'].mean()
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),
360
- xytext=(label_x, label_y),
361
  textcoords='data',
362
  bbox=dict(
363
  facecolor='white',
@@ -369,64 +379,60 @@ class ReportGenerator:
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
376
  )
377
  )
 
378
 
379
  # Configurações finais
380
  plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
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, 6))
402
- plt.subplots_adjust(
403
- left=0.07,
404
- right=0.93,
405
- top=0.92,
406
- bottom=0.12
407
- )
408
-
409
  ax = plt.gca()
 
 
410
  plt.grid(True, alpha=0.2, linestyle='--')
411
  ax.set_facecolor('#f8f9fa')
412
-
413
- def calculate_distance(p1, p2):
414
- return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
415
-
416
- # Estruturas para armazenamento
417
- points_by_level = {level: [] for level in self.colors.keys()}
418
- used_positions = []
419
-
420
- # Coletar e plotar pontos
421
  for nivel, color in self.colors.items():
422
  mask = self.data['Nível'] == nivel
423
  tarefas = self.data[mask]['Tarefas Completadas']
424
  acertos = self.data[mask]['Acertos Absolutos']
425
-
426
- plt.scatter(tarefas, acertos, c=color, label=nivel, alpha=0.7, s=100)
427
-
428
  for t, a, nome in zip(tarefas, acertos, self.data[mask]['Nome do Aluno']):
429
- points_by_level[nivel].append((t, a, nome))
 
 
 
 
430
 
431
  # Linha de tendência
432
  z = np.polyfit(self.data['Tarefas Completadas'],
@@ -445,41 +451,45 @@ class ReportGenerator:
445
  plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
446
  label='Média de Acertos')
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))
453
- if key not in point_groups:
454
- point_groups[key] = []
455
- point_groups[key].append((t, a, nome))
456
-
457
- # Processar cada grupo
458
- for (x, y), group in point_groups.items():
459
- if len(group) == 1:
460
- label = group[0][2].split()[0]
461
- else:
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(
480
- label,
481
  (x, y),
482
- xytext=(label_x, label_y),
483
  textcoords='data',
484
  bbox=dict(
485
  facecolor='white',
@@ -491,21 +501,46 @@ class ReportGenerator:
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
498
  )
499
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
 
501
  # Configurações finais
502
  plt.title('Relação entre Tarefas Completadas e Acertos', pad=20, fontsize=14)
503
  plt.xlabel('Número de Tarefas Completadas', fontsize=12)
504
  plt.ylabel('Número de Acertos', fontsize=12)
505
-
506
- # Forçar ticks inteiros no eixo x
507
- ax.xaxis.set_major_locator(MaxNLocator(integer=True))
508
-
509
  # Ajustar limites
510
  plt.xlim(
511
  self.data['Tarefas Completadas'].min() - 1,
@@ -515,22 +550,20 @@ class ReportGenerator:
515
  max(0, self.data['Acertos Absolutos'].min() - 1),
516
  self.data['Acertos Absolutos'].max() + 2
517
  )
518
-
519
- # Legenda
520
  handles, labels = plt.gca().get_legend_handles_labels()
521
  by_label = dict(zip(labels, handles))
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:
 
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 com estilo limpo
275
  plt.grid(True, alpha=0.2, linestyle='--')
276
  ax.set_facecolor('#f8f9fa')
277
+
278
+ # Linha de média
279
+ media_acertos = self.data['Acertos Absolutos'].mean()
280
+ plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
281
+ label='Média de Acertos')
282
 
283
  # Estruturas para armazenamento
284
+ all_points = []
285
+ point_groups = {}
286
+
287
+ # Plotar pontos e coletar dados
288
  for nivel, color in self.colors.items():
289
  mask = self.data['Nível'] == nivel
290
  tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
291
  acertos = self.data[mask]['Acertos Absolutos']
292
 
293
+ scatter = plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=100)
294
 
295
  for t, a, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
296
+ point_key = (round(t, 1), a)
297
+ if point_key not in point_groups:
298
+ point_groups[point_key] = []
299
+ point_groups[point_key].append((t, a, nome, color))
300
+ all_points.append((t, a))
301
+
302
+ # Lista para controle de sobreposições
303
+ annotations = []
304
+
305
+ # Função para calcular melhor posição
306
+ def get_best_position(x, y, existing_annotations):
307
+ angles = np.linspace(0, 2*np.pi, 16) # 16 direções possíveis
308
+ base_radius = 3.0
309
+ max_radius = 6.0
310
+ best_pos = None
311
+ min_overlap = float('inf')
312
 
313
+ for radius in np.linspace(base_radius, max_radius, 4):
314
+ for angle in angles:
315
+ new_x = x + radius * np.cos(angle)
316
+ new_y = y + radius * np.sin(angle)
317
+
318
+ # Verificar limites do gráfico
319
+ if not (ax.get_xlim()[0] <= new_x <= ax.get_xlim()[1] and
320
+ ax.get_ylim()[0] <= new_y <= ax.get_ylim()[1]):
321
+ continue
322
+
323
+ overlaps = sum(1 for ann in existing_annotations if
324
+ abs(ann[0] - new_x) < 2 and abs(ann[1] - new_y) < 2)
325
+
326
+ if overlaps < min_overlap:
327
+ min_overlap = overlaps
328
+ best_pos = (new_x, new_y)
329
+
330
+ return best_pos or (x + base_radius, y + base_radius)
331
 
332
+ # Adicionar anotações
333
+ for key, group in point_groups.items():
334
+ if len(group) == 1:
335
+ x, y, nome, _ = group[0]
336
+ new_pos = get_best_position(x, y, annotations)
337
+
338
+ plt.annotate(
339
+ nome.split()[0],
340
+ (x, y),
341
+ xytext=new_pos,
342
+ textcoords='data',
343
+ bbox=dict(
344
+ facecolor='white',
345
+ edgecolor='lightgray',
346
+ alpha=0.95,
347
+ pad=1.0,
348
+ boxstyle='round,pad=0.8'
349
+ ),
350
+ fontsize=9,
351
+ arrowprops=dict(
352
+ arrowstyle='-|>',
353
+ connectionstyle='arc3,rad=0.2',
354
+ color='gray',
355
+ alpha=0.6
356
+ )
357
+ )
358
+ annotations.append(new_pos)
359
+ else:
360
  # Calcular centroide do grupo
361
+ x_center = sum(p[0] for p in group) / len(group)
362
+ y_center = sum(p[1] for p in group) / len(group)
363
+ nomes = sorted(set([p[2].split()[0] for p in group]))
364
 
365
+ new_pos = get_best_position(x_center, y_center, annotations)
 
 
 
 
 
 
 
 
 
 
 
366
 
 
 
 
 
 
 
 
 
 
 
367
  plt.annotate(
368
+ f"{len(group)} alunos:\n" + "\n".join(nomes),
369
+ (x_center, y_center),
370
+ xytext=new_pos,
371
  textcoords='data',
372
  bbox=dict(
373
  facecolor='white',
 
379
  fontsize=9,
380
  arrowprops=dict(
381
  arrowstyle='-|>',
382
+ connectionstyle='arc3,rad=0.2',
383
  color='gray',
384
+ alpha=0.6
 
385
  )
386
  )
387
+ annotations.append(new_pos)
388
 
389
  # Configurações finais
390
  plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
391
  plt.xlabel('Tempo Total (minutos)', fontsize=12)
392
  plt.ylabel('Número de Acertos', fontsize=12)
393
+
394
+ # Legenda com todos os elementos
395
  handles, labels = plt.gca().get_legend_handles_labels()
396
  by_label = dict(zip(labels, handles))
397
+ plt.legend(by_label.values(), by_label.keys(),
398
+ bbox_to_anchor=(1.05, 1),
399
+ loc='upper left',
400
+ borderaxespad=0,
401
+ frameon=True,
402
+ fancybox=True)
403
+
404
+ plt.tight_layout()
 
 
405
  return plt.gcf()
406
 
407
  def create_tasks_performance_plot(self) -> plt.Figure:
408
  """Cria o gráfico de relação entre tarefas e acertos com visualização otimizada."""
409
+ plt.figure(figsize=(15, 10))
 
 
 
 
 
 
 
410
  ax = plt.gca()
411
+
412
+ # Configuração inicial
413
  plt.grid(True, alpha=0.2, linestyle='--')
414
  ax.set_facecolor('#f8f9fa')
415
+
416
+ # Forçar ticks inteiros no eixo x
417
+ ax.xaxis.set_major_locator(MaxNLocator(integer=True))
418
+
419
+ point_groups = {}
420
+ all_points = []
421
+
422
+ # Plotar pontos e coletar dados
 
423
  for nivel, color in self.colors.items():
424
  mask = self.data['Nível'] == nivel
425
  tarefas = self.data[mask]['Tarefas Completadas']
426
  acertos = self.data[mask]['Acertos Absolutos']
427
+
428
+ scatter = plt.scatter(tarefas, acertos, c=color, label=nivel, alpha=0.7, s=100)
429
+
430
  for t, a, nome in zip(tarefas, acertos, self.data[mask]['Nome do Aluno']):
431
+ point_key = (t, a)
432
+ if point_key not in point_groups:
433
+ point_groups[point_key] = []
434
+ point_groups[point_key].append((t, a, nome, color))
435
+ all_points.append((t, a))
436
 
437
  # Linha de tendência
438
  z = np.polyfit(self.data['Tarefas Completadas'],
 
451
  plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
452
  label='Média de Acertos')
453
 
454
+ # Lista para controle de sobreposições
455
+ annotations = []
456
+
457
+ # Função para calcular melhor posição
458
+ def get_best_position(x, y, existing_annotations):
459
+ angles = np.linspace(0, 2*np.pi, 16)
460
+ base_radius = 2.0 # Menor para tarefas discretas
461
+ max_radius = 4.0
462
+ best_pos = None
463
+ min_overlap = float('inf')
464
+
465
+ for radius in np.linspace(base_radius, max_radius, 4):
466
+ for angle in angles:
467
+ new_x = x + radius * np.cos(angle)
468
+ new_y = y + radius * np.sin(angle)
469
+
470
+ if not (ax.get_xlim()[0] <= new_x <= ax.get_xlim()[1] and
471
+ ax.get_ylim()[0] <= new_y <= ax.get_ylim()[1]):
472
+ continue
473
+
474
+ overlaps = sum(1 for ann in existing_annotations if
475
+ abs(ann[0] - new_x) < 1.5 and abs(ann[1] - new_y) < 1.5)
476
+
477
+ if overlaps < min_overlap:
478
+ min_overlap = overlaps
479
+ best_pos = (new_x, new_y)
480
+
481
+ return best_pos or (x + base_radius, y + base_radius)
 
482
 
483
+ # Adicionar anotações
484
+ for key, group in point_groups.items():
485
+ if len(group) == 1:
486
+ x, y, nome, _ = group[0]
487
+ new_pos = get_best_position(x, y, annotations)
488
+
489
  plt.annotate(
490
+ nome.split()[0],
491
  (x, y),
492
+ xytext=new_pos,
493
  textcoords='data',
494
  bbox=dict(
495
  facecolor='white',
 
501
  fontsize=9,
502
  arrowprops=dict(
503
  arrowstyle='-|>',
504
+ connectionstyle='arc3,rad=0.2',
505
  color='gray',
506
+ alpha=0.6
 
507
  )
508
  )
509
+ annotations.append(new_pos)
510
+ else:
511
+ x_center = sum(p[0] for p in group) / len(group)
512
+ y_center = sum(p[1] for p in group) / len(group)
513
+ nomes = sorted(set([p[2].split()[0] for p in group]))
514
+
515
+ new_pos = get_best_position(x_center, y_center, annotations)
516
+
517
+ plt.annotate(
518
+ f"{len(group)} alunos com\n{group[0][0]:.0f} tarefas:\n" + "\n".join(nomes),
519
+ (x_center, y_center),
520
+ xytext=new_pos,
521
+ textcoords='data',
522
+ bbox=dict(
523
+ facecolor='white',
524
+ edgecolor='lightgray',
525
+ alpha=0.95,
526
+ pad=1.0,
527
+ boxstyle='round,pad=0.8'
528
+ ),
529
+ fontsize=9,
530
+ arrowprops=dict(
531
+ arrowstyle='-|>',
532
+ connectionstyle='arc3,rad=0.2',
533
+ color='gray',
534
+ alpha=0.6
535
+ )
536
+ )
537
+ annotations.append(new_pos)
538
 
539
  # Configurações finais
540
  plt.title('Relação entre Tarefas Completadas e Acertos', pad=20, fontsize=14)
541
  plt.xlabel('Número de Tarefas Completadas', fontsize=12)
542
  plt.ylabel('Número de Acertos', fontsize=12)
543
+
 
 
 
544
  # Ajustar limites
545
  plt.xlim(
546
  self.data['Tarefas Completadas'].min() - 1,
 
550
  max(0, self.data['Acertos Absolutos'].min() - 1),
551
  self.data['Acertos Absolutos'].max() + 2
552
  )
553
+
554
+ # Legenda com todos os elementos
555
  handles, labels = plt.gca().get_legend_handles_labels()
556
  by_label = dict(zip(labels, handles))
557
+ plt.legend(by_label.values(), by_label.keys(),
558
+ bbox_to_anchor=(1.05, 1),
559
+ loc='upper left',
560
+ borderaxespad=0,
561
+ frameon=True,
562
+ fancybox=True)
 
 
 
 
 
563
 
564
+ plt.tight_layout()
565
+ return plt.gcf()
566
+
567
  def generate_graphs(self) -> List[plt.Figure]:
568
  """Gera todos os gráficos para o relatório."""
569
  try: