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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -168
app.py CHANGED
@@ -270,47 +270,52 @@ 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 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,19 +328,21 @@ class ReportGenerator:
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,25 +360,11 @@ class ReportGenerator:
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,141 +377,75 @@ class ReportGenerator:
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()
 
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
  ),
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
  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
  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()