Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -270,7 +270,7 @@ class ReportGenerator:
|
|
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')
|
@@ -278,24 +278,18 @@ class ReportGenerator:
|
|
278 |
def calculate_distance(p1, p2):
|
279 |
return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
280 |
|
281 |
-
def create_bezier_curve(start, end):
|
282 |
-
mid_x = (start[0] + end[0]) / 2
|
283 |
-
return f'M{start[0]},{start[1]} ' \
|
284 |
-
f'C{mid_x},{start[1]} {mid_x},{end[1]} {end[0]},{end[1]}'
|
285 |
-
|
286 |
# Estruturas para armazenamento
|
287 |
points_by_level = {level: [] for level in self.colors.keys()}
|
288 |
label_positions = {}
|
289 |
-
connection_lines = []
|
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,36 +298,36 @@ class ReportGenerator:
|
|
304 |
plt.axhline(y=media_acertos, color='gray', linestyle=':', alpha=0.5,
|
305 |
label='Média de Acertos')
|
306 |
|
307 |
-
# Processamento por nível
|
308 |
for nivel, points in points_by_level.items():
|
309 |
-
# Ordenar pontos por Y
|
310 |
points.sort(key=lambda p: (p[1], p[0]))
|
311 |
-
|
312 |
# Agrupar pontos próximos
|
313 |
groups = []
|
314 |
used = set()
|
315 |
-
|
316 |
for i, p1 in enumerate(points):
|
317 |
if i in used:
|
318 |
continue
|
319 |
-
|
320 |
current_group = [p1]
|
321 |
used.add(i)
|
322 |
-
|
323 |
for j, p2 in enumerate(points):
|
324 |
if j not in used and calculate_distance(p1[:2], p2[:2]) < 2.0:
|
325 |
current_group.append(p2)
|
326 |
used.add(j)
|
327 |
-
|
328 |
groups.append(current_group)
|
329 |
|
330 |
# Processar cada grupo
|
331 |
for group in groups:
|
332 |
-
# Calcular centroide
|
333 |
center_x = sum(p[0] for p in group) / len(group)
|
334 |
center_y = sum(p[1] for p in group) / len(group)
|
335 |
-
|
336 |
-
# Determinar
|
337 |
if len(group) == 1:
|
338 |
x, y, nome = group[0]
|
339 |
label_text = nome.split()[0]
|
@@ -341,14 +335,14 @@ class ReportGenerator:
|
|
341 |
x, y = center_x, center_y
|
342 |
label_text = '\n'.join(sorted(p[2].split()[0] for p in group))
|
343 |
|
344 |
-
# Calcular posição
|
345 |
angle = np.random.uniform(0, 2*np.pi)
|
346 |
base_radius = 3.0 + len(group) * 0.5
|
347 |
-
|
348 |
label_x = x + base_radius * np.cos(angle)
|
349 |
label_y = y + base_radius * np.sin(angle)
|
350 |
-
|
351 |
-
# Ajustar posição se
|
352 |
while any(calculate_distance((label_x, label_y), pos) < 2.0
|
353 |
for pos in label_positions.values()):
|
354 |
angle += np.pi/4
|
@@ -357,8 +351,8 @@ class ReportGenerator:
|
|
357 |
|
358 |
# Guardar posição do label
|
359 |
label_positions[(x, y)] = (label_x, label_y)
|
360 |
-
|
361 |
-
# Criar anotação com
|
362 |
plt.annotate(
|
363 |
label_text,
|
364 |
(x, y),
|
@@ -374,27 +368,24 @@ class ReportGenerator:
|
|
374 |
fontsize=9,
|
375 |
arrowprops=dict(
|
376 |
arrowstyle='-|>',
|
377 |
-
connectionstyle=
|
378 |
color='gray',
|
379 |
alpha=0.6,
|
380 |
mutation_scale=15
|
381 |
)
|
382 |
)
|
383 |
-
|
384 |
-
# Registrar linha de conexão
|
385 |
-
connection_lines.append(((x, y), (label_x, label_y)))
|
386 |
|
387 |
# Configurações finais
|
388 |
plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
|
389 |
plt.xlabel('Tempo Total (minutos)', fontsize=12)
|
390 |
plt.ylabel('Número de Acertos', fontsize=12)
|
391 |
-
|
392 |
-
# Ajustar limites
|
393 |
x_min, x_max = plt.xlim()
|
394 |
y_min, y_max = plt.ylim()
|
395 |
plt.xlim(x_min - 1, x_max + 1)
|
396 |
plt.ylim(max(0, y_min - 1), y_max + 1)
|
397 |
-
|
398 |
# Legenda
|
399 |
handles, labels = plt.gca().get_legend_handles_labels()
|
400 |
by_label = dict(zip(labels, handles))
|
@@ -407,7 +398,7 @@ class ReportGenerator:
|
|
407 |
frameon=True,
|
408 |
fancybox=True
|
409 |
)
|
410 |
-
|
411 |
plt.tight_layout()
|
412 |
return plt.gcf()
|
413 |
|
|
|
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')
|
|
|
278 |
def calculate_distance(p1, p2):
|
279 |
return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
280 |
|
|
|
|
|
|
|
|
|
|
|
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 |
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]
|
|
|
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
|
|
|
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 |
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
|
375 |
)
|
376 |
)
|
|
|
|
|
|
|
377 |
|
378 |
# Configurações finais
|
379 |
plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=14)
|
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))
|
|
|
398 |
frameon=True,
|
399 |
fancybox=True
|
400 |
)
|
401 |
+
|
402 |
plt.tight_layout()
|
403 |
return plt.gcf()
|
404 |
|