histlearn commited on
Commit
6cc9da1
·
verified ·
1 Parent(s): 75dfa94

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -95
app.py CHANGED
@@ -270,52 +270,47 @@ class ReportGenerator:
270
  """Cria o gráfico de relação entre tempo e acertos com agrupamento melhorado."""
271
  plt.figure(figsize=(15, 10))
272
 
273
- # Configuração inicial
274
  plt.grid(True, alpha=0.2, linestyle='--')
275
  plt.gca().set_facecolor('#f8f9fa')
276
 
277
- # Função para calcular distância entre pontos
278
- def calculate_distance(p1, p2):
279
- return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
 
 
280
 
281
- # Lista para armazenar todos os pontos
282
- all_points = []
 
 
 
 
 
283
 
284
- # Primeiro passar, plotar pontos e coletar coordenadas
285
  for nivel, color in self.colors.items():
286
  mask = self.data['Nível'] == nivel
287
  tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
288
  acertos = self.data[mask]['Acertos Absolutos']
289
 
290
- plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=100)
291
 
 
292
  for t, a, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
293
- all_points.append((t, a, nome, color))
 
 
 
294
 
295
- # Agrupar pontos próximos
296
- distance_threshold = 1.5 # Ajuste este valor conforme necessário
297
- grouped_points = []
298
- processed = set()
299
-
300
- for i, (x1, y1, name1, color1) in enumerate(all_points):
301
- if i in processed:
302
- continue
303
-
304
- current_group = [(x1, y1, name1, color1)]
305
- processed.add(i)
306
-
307
- for j, (x2, y2, name2, color2) in enumerate(all_points[i+1:], i+1):
308
- if j not in processed and calculate_distance((x1, y1), (x2, y2)) < distance_threshold:
309
- current_group.append((x2, y2, name2, color2))
310
- processed.add(j)
311
-
312
- grouped_points.append(current_group)
313
 
314
- # Adicionar anotações
315
- for group in grouped_points:
316
- if len(group) == 1:
317
- x, y, nome, _ = group[0]
318
- plt.annotate(
319
  nome.split()[0],
320
  (x, y),
321
  xytext=(5, 5),
@@ -328,21 +323,19 @@ class ReportGenerator:
328
  ),
329
  fontsize=8
330
  )
 
331
  else:
332
- # Calcular centro do grupo
333
- x_center = sum(p[0] for p in group) / len(group)
334
- y_center = sum(p[1] for p in group) / len(group)
335
 
336
- # Ordenar nomes alfabeticamente
337
- nomes = sorted([p[2].split()[0] for p in group])
 
338
 
339
- # Determinar direção do deslocamento baseado no quadrante
340
- x_direction = 30 if x_center < np.mean([p[0] for p in all_points]) else -30
341
- y_direction = 20 if y_center < np.mean([p[1] for p in all_points]) else -20
342
-
343
- plt.annotate(
344
  '\n'.join(nomes),
345
- (x_center, y_center),
346
  xytext=(x_direction, y_direction),
347
  textcoords='offset points',
348
  bbox=dict(
@@ -360,11 +353,25 @@ class ReportGenerator:
360
  alpha=0.6
361
  )
362
  )
 
 
 
 
 
 
 
 
 
 
 
363
 
364
- plt.title('Relação entre Tempo e Acertos por Nível', pad=20)
365
- plt.xlabel('Tempo Total (minutos)')
366
- plt.ylabel('Número de Acertos')
367
  plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
 
 
 
368
  plt.tight_layout()
369
 
370
  return plt.gcf()
@@ -377,75 +384,141 @@ class ReportGenerator:
377
  plt.grid(True, alpha=0.2, linestyle='--')
378
  plt.gca().set_facecolor('#f8f9fa')
379
 
380
- # Dicionário para agrupar pontos com mesmas coordenadas
 
 
 
 
 
 
381
  point_groups = {}
 
382
 
383
- # Primeiro passar, plotar pontos
384
  for nivel, color in self.colors.items():
385
  mask = self.data['Nível'] == nivel
386
  tarefas = self.data[mask]['Tarefas Completadas']
387
  acertos = self.data[mask]['Acertos Absolutos']
388
 
389
- plt.scatter(tarefas, acertos, c=color, label=nivel, alpha=0.7, s=100)
390
-
391
- # Agrupar pontos com coordenadas idênticas
392
  for t, a, nome in zip(tarefas, acertos, self.data[mask]['Nome do Aluno']):
393
- key = (t, a)
394
- if key not in point_groups:
395
- point_groups[key] = []
396
- point_groups[key].append((nome, color))
 
397
 
398
- # Linha de tendência
399
  z = np.polyfit(self.data['Tarefas Completadas'],
400
- self.data['Acertos Absolutos'], 1)
401
  p = np.poly1d(z)
402
  x_range = np.linspace(
403
- self.data['Tarefas Completadas'].min(),
404
- self.data['Tarefas Completadas'].max(),
405
  100
406
  )
407
  plt.plot(x_range, p(x_range), "--", color='#e74c3c', alpha=0.8,
408
  label='Tendência', linewidth=2)
409
 
410
- # Adicionar anotações
411
- for (x, y), group in point_groups.items():
412
- nomes = sorted([nome.split()[0] for nome, _ in group])
413
-
414
- # Ajustar posição baseado na densidade de pontos na região
415
- nearby_points = sum(1 for (px, py) in point_groups.keys()
416
- if abs(px - x) <= 1 and abs(py - y) <= 1)
417
-
418
- angle = (hash(str(nomes)) % 8) * 45 # Ângulo baseado nos nomes
419
- radius = 20 + (nearby_points * 5) # Raio aumenta com pontos próximos
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
- dx = radius * np.cos(np.radians(angle))
422
- dy = radius * np.sin(np.radians(angle))
423
-
424
- plt.annotate(
425
- '\n'.join(nomes),
426
- (x, y),
427
- xytext=(dx, dy),
428
- textcoords='offset points',
429
- bbox=dict(
430
- facecolor='white',
431
- edgecolor='lightgray',
432
- alpha=0.9,
433
- pad=0.5,
434
- boxstyle='round,pad=0.5'
435
- ),
436
- fontsize=8,
437
- arrowprops=dict(
438
- arrowstyle='-|>',
439
- connectionstyle=f'arc3,rad={0.2 if dx > 0 else -0.2}',
440
- color='gray',
441
- alpha=0.6
442
  )
443
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
- plt.title('Relação entre Tarefas Completadas e Acertos', pad=20)
446
- plt.xlabel('Número de Tarefas Completadas')
447
- plt.ylabel('Número de Acertos')
448
- plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
449
  plt.tight_layout()
450
 
451
  return plt.gcf()
 
270
  """Cria o gráfico de relação entre tempo e acertos com agrupamento melhorado."""
271
  plt.figure(figsize=(15, 10))
272
 
273
+ # Configuração inicial com estilo mais limpo
274
  plt.grid(True, alpha=0.2, linestyle='--')
275
  plt.gca().set_facecolor('#f8f9fa')
276
 
277
+ # Função para calcular centroide de um grupo de pontos
278
+ def calculate_centroid(points):
279
+ x = sum(p[0] for p in points) / len(points)
280
+ y = sum(p[1] for p in points) / len(points)
281
+ return (x, y)
282
 
283
+ # Função para verificar sobreposição de textos
284
+ def texts_overlap(bbox1, bbox2, min_dist=40):
285
+ return (abs(bbox1.x0 - bbox2.x0) < min_dist and
286
+ abs(bbox1.y0 - bbox2.y0) < min_dist)
287
+
288
+ # Estrutura para armazenar pontos e suas informações
289
+ point_groups = {}
290
 
291
+ # Plotar pontos e coletar informações
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
+ scatter = plt.scatter(tempo, acertos, c=color, label=nivel, alpha=0.7, s=100)
298
 
299
+ # Agrupar pontos próximos
300
  for t, a, nome in zip(tempo, acertos, self.data[mask]['Nome do Aluno']):
301
+ point_key = (round(t, 1), a) # Arredondar tempo para agrupar pontos próximos
302
+ if point_key not in point_groups:
303
+ point_groups[point_key] = []
304
+ point_groups[point_key].append((t, a, nome, color))
305
 
306
+ # Lista para armazenar todos os textos para ajuste posterior
307
+ texts = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
+ # Adicionar anotações com posicionamento otimizado
310
+ for points in point_groups.values():
311
+ if len(points) == 1:
312
+ x, y, nome, _ = points[0]
313
+ text = plt.annotate(
314
  nome.split()[0],
315
  (x, y),
316
  xytext=(5, 5),
 
323
  ),
324
  fontsize=8
325
  )
326
+ texts.append(text)
327
  else:
328
+ # Calcular posição central do grupo
329
+ cx, cy = calculate_centroid([(p[0], p[1]) for p in points])
330
+ nomes = sorted([p[2].split()[0] for p in points])
331
 
332
+ # Determinar direção baseada na posição no gráfico
333
+ x_direction = 30 if cx < np.mean([p[0] for p in points]) else -30
334
+ y_direction = 20 if cy < np.mean([p[1] for p in points]) else -20
335
 
336
+ text = plt.annotate(
 
 
 
 
337
  '\n'.join(nomes),
338
+ (cx, cy),
339
  xytext=(x_direction, y_direction),
340
  textcoords='offset points',
341
  bbox=dict(
 
353
  alpha=0.6
354
  )
355
  )
356
+ texts.append(text)
357
+
358
+ # Ajustar posições dos textos para evitar sobreposições
359
+ adjust_text(
360
+ texts,
361
+ expand_points=(1.2, 1.2),
362
+ expand_text=(1.1, 1.1),
363
+ force_points=(0.5, 0.5),
364
+ force_text=(0.5, 0.5),
365
+ arrowprops=dict(arrowstyle='->', color='gray', alpha=0.6)
366
+ )
367
 
368
+ plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
369
+ plt.xlabel('Tempo Total (minutos)', fontsize=12)
370
+ plt.ylabel('Número de Acertos', fontsize=12)
371
  plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
372
+
373
+ # Ajustar limites e margens
374
+ plt.margins(x=0.1, y=0.1)
375
  plt.tight_layout()
376
 
377
  return plt.gcf()
 
384
  plt.grid(True, alpha=0.2, linestyle='--')
385
  plt.gca().set_facecolor('#f8f9fa')
386
 
387
+ # Função para calcular centroide de um grupo de pontos
388
+ def calculate_centroid(points):
389
+ x = sum(p[0] for p in points) / len(points)
390
+ y = sum(p[1] for p in points) / len(points)
391
+ return (x, y)
392
+
393
+ # Estrutura para armazenar pontos e suas informações
394
  point_groups = {}
395
+ all_points = []
396
 
397
+ # Plotar pontos e coletar informações
398
  for nivel, color in self.colors.items():
399
  mask = self.data['Nível'] == nivel
400
  tarefas = self.data[mask]['Tarefas Completadas']
401
  acertos = self.data[mask]['Acertos Absolutos']
402
 
403
+ scatter = plt.scatter(tarefas, acertos, c=color, label=nivel, alpha=0.7, s=100)
404
+
405
+ # Agrupar pontos com coordenadas idênticas ou muito próximas
406
  for t, a, nome in zip(tarefas, acertos, self.data[mask]['Nome do Aluno']):
407
+ point_key = (t, a) # Pontos exatamente iguais
408
+ if point_key not in point_groups:
409
+ point_groups[point_key] = []
410
+ point_groups[point_key].append((t, a, nome, color))
411
+ all_points.append((t, a))
412
 
413
+ # Calcular e plotar linha de tendência
414
  z = np.polyfit(self.data['Tarefas Completadas'],
415
+ self.data['Acertos Absolutos'], 1)
416
  p = np.poly1d(z)
417
  x_range = np.linspace(
418
+ self.data['Tarefas Completadas'].min() - 0.5,
419
+ self.data['Tarefas Completadas'].max() + 0.5,
420
  100
421
  )
422
  plt.plot(x_range, p(x_range), "--", color='#e74c3c', alpha=0.8,
423
  label='Tendência', linewidth=2)
424
 
425
+ # Lista para armazenar todos os textos para ajuste posterior
426
+ texts = []
427
+
428
+ # Calcular média de acertos para referência
429
+ media_acertos = self.data['Acertos Absolutos'].mean()
430
+ plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
431
+ label='Média de Acertos')
432
+
433
+ # Adicionar anotações com posicionamento otimizado
434
+ for points in point_groups.values():
435
+ if len(points) == 1:
436
+ x, y, nome, _ = points[0]
437
+ text = plt.annotate(
438
+ nome.split()[0],
439
+ (x, y),
440
+ xytext=(5, 5),
441
+ textcoords='offset points',
442
+ bbox=dict(
443
+ facecolor='white',
444
+ edgecolor='none',
445
+ alpha=0.8,
446
+ pad=0.5
447
+ ),
448
+ fontsize=8
449
+ )
450
+ texts.append(text)
451
+ else:
452
+ # Calcular posição central do grupo
453
+ cx, cy = calculate_centroid([(p[0], p[1]) for p in points])
454
+ nomes = sorted([p[2].split()[0] for p in points])
455
+
456
+ # Calcular direção baseada na densidade de pontos na vizinhança
457
+ nearby_points = sum(1 for px, py in all_points
458
+ if abs(px - cx) <= 1 and abs(py - cy) <= 1)
459
+ angle = (hash(str(nomes)) % 8) * 45 # Ângulo variável baseado nos nomes
460
+ radius = 20 + (nearby_points * 5) # Raio aumenta com a densidade
461
+
462
+ x_direction = radius * np.cos(np.radians(angle))
463
+ y_direction = radius * np.sin(np.radians(angle))
464
 
465
+ text = plt.annotate(
466
+ '\n'.join(nomes),
467
+ (cx, cy),
468
+ xytext=(x_direction, y_direction),
469
+ textcoords='offset points',
470
+ bbox=dict(
471
+ facecolor='white',
472
+ edgecolor='lightgray',
473
+ alpha=0.9,
474
+ pad=0.5,
475
+ boxstyle='round,pad=0.5'
476
+ ),
477
+ fontsize=8,
478
+ arrowprops=dict(
479
+ arrowstyle='-|>',
480
+ connectionstyle=f'arc3,rad={0.2 if x_direction > 0 else -0.2}',
481
+ color='gray',
482
+ alpha=0.6
483
+ )
 
 
484
  )
485
+ texts.append(text)
486
+
487
+ # Ajustar posições dos textos para evitar sobreposições
488
+ adjust_text(
489
+ texts,
490
+ expand_points=(1.2, 1.2),
491
+ expand_text=(1.1, 1.1),
492
+ force_points=(0.5, 0.5),
493
+ force_text=(0.5, 0.5),
494
+ arrowprops=dict(arrowstyle='->', color='gray', alpha=0.6)
495
+ )
496
+
497
+ # Configurações finais do gráfico
498
+ plt.title('Relação entre Tarefas Completadas e Acertos', pad=20, fontsize=14)
499
+ plt.xlabel('Número de Tarefas Completadas', fontsize=12)
500
+ plt.ylabel('Número de Acertos', fontsize=12)
501
+
502
+ # Ajustar limites dos eixos para melhor visualização
503
+ plt.xlim(
504
+ self.data['Tarefas Completadas'].min() - 1,
505
+ self.data['Tarefas Completadas'].max() + 1
506
+ )
507
+ plt.ylim(
508
+ self.data['Acertos Absolutos'].min() - 1,
509
+ self.data['Acertos Absolutos'].max() + 2
510
+ )
511
+
512
+ # Configurar legenda com todos os elementos
513
+ plt.legend(
514
+ bbox_to_anchor=(1.05, 1),
515
+ loc='upper left',
516
+ borderaxespad=0,
517
+ frameon=True,
518
+ fancybox=True,
519
+ shadow=True
520
+ )
521
 
 
 
 
 
522
  plt.tight_layout()
523
 
524
  return plt.gcf()