Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -248,45 +248,16 @@ def gerar_paleta_cores(n_cores):
|
|
248 |
return cores_base[:n_cores]
|
249 |
|
250 |
def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arquivo=None):
|
251 |
-
"""Plota gráfico de evolução das notas por bimestre com visualização refinada."""
|
252 |
n_disciplinas = len(disciplinas_dados)
|
253 |
|
254 |
-
if n_disciplinas == 0:
|
255 |
-
raise ValueError("Nenhuma disciplina válida encontrada para plotar.")
|
256 |
-
|
257 |
plt.figure(figsize=(11.69, 8.27))
|
258 |
-
|
259 |
cores = gerar_paleta_cores(n_disciplinas)
|
260 |
-
marcadores = ['o', 's', '^', 'D', 'v', '<', '>', 'p', '
|
261 |
-
estilos_linha = ['-', '--', '-.', ':'
|
262 |
|
263 |
-
plt.grid(True, linestyle='--', alpha=0.3, zorder=0)
|
264 |
-
|
265 |
-
# Deslocamento ainda menor e mais refinado
|
266 |
deslocamentos = np.linspace(-0.03, 0.03, n_disciplinas)
|
267 |
-
|
268 |
-
# Estrutura para armazenar as posições das anotações já utilizadas
|
269 |
-
anotacoes_usadas = {} # formato: {bimestre: [(y, texto)]}
|
270 |
-
|
271 |
-
# Primeira passagem: coletar todos os valores e determinar grupos
|
272 |
-
grupos_notas = {} # {bimestre: {nota: [índices]}}
|
273 |
-
for idx, disc_data in enumerate(disciplinas_dados):
|
274 |
-
notas = pd.Series(disc_data['notas'])
|
275 |
-
bimestres_cursados = disc_data['bimestres_cursados']
|
276 |
-
|
277 |
-
if bimestres_cursados:
|
278 |
-
notas_validas = [nota for i, nota in enumerate(notas, 1) if i in bimestres_cursados and nota is not None]
|
279 |
-
bimestres = [bim for bim in bimestres_cursados if notas[bim-1] is not None]
|
280 |
-
|
281 |
-
for bim, nota in zip(bimestres, notas_validas):
|
282 |
-
if nota is not None:
|
283 |
-
if bim not in grupos_notas:
|
284 |
-
grupos_notas[bim] = {}
|
285 |
-
if nota not in grupos_notas[bim]:
|
286 |
-
grupos_notas[bim][nota] = []
|
287 |
-
grupos_notas[bim][nota].append(idx)
|
288 |
|
289 |
-
# Segunda passagem: plotar e anotar
|
290 |
for idx, disc_data in enumerate(disciplinas_dados):
|
291 |
notas = pd.Series(disc_data['notas'])
|
292 |
bimestres_cursados = disc_data['bimestres_cursados']
|
@@ -298,164 +269,75 @@ def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arq
|
|
298 |
bimestres_deslocados = [bim + desloc for bim in bimestres]
|
299 |
|
300 |
if notas_validas:
|
301 |
-
# Plotar linha e pontos
|
302 |
plt.plot(bimestres_deslocados, notas_validas,
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
alpha=0.8)
|
310 |
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
# Verificar sobreposição com anotações existentes
|
326 |
-
while any(abs(y - (y_base + y_offset/20)) < 0.4 for y, _ in anotacoes_usadas.get(bim_orig, [])):
|
327 |
-
y_offset += 5
|
328 |
-
|
329 |
-
# Adicionar anotação
|
330 |
-
plt.annotate(texto,
|
331 |
-
(bim_orig, nota),
|
332 |
-
textcoords="offset points",
|
333 |
-
xytext=(0, y_offset),
|
334 |
-
ha='center',
|
335 |
-
va='bottom',
|
336 |
-
fontsize=8,
|
337 |
-
bbox=dict(facecolor='white',
|
338 |
-
edgecolor='none',
|
339 |
-
alpha=0.8,
|
340 |
-
pad=0.5))
|
341 |
-
|
342 |
-
anotacoes_usadas[bim_orig].append((nota + y_offset/20, texto))
|
343 |
-
|
344 |
-
# Usar título personalizado se fornecido
|
345 |
-
titulo_grafico = titulo or 'Evolução das Médias por Disciplina ao Longo dos Bimestres'
|
346 |
-
plt.title(titulo_grafico, pad=20, fontsize=12, fontweight='bold')
|
347 |
-
|
348 |
-
plt.xlabel('Bimestres', fontsize=10)
|
349 |
-
plt.ylabel('Notas', fontsize=10)
|
350 |
plt.xticks([1, 2, 3, 4], ['1º Bim', '2º Bim', '3º Bim', '4º Bim'])
|
351 |
plt.ylim(0, ESCALA_MAXIMA_NOTAS)
|
352 |
-
|
353 |
-
# Adicionar linha de aprovação
|
354 |
-
plt.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
|
355 |
-
plt.text(0.02, LIMITE_APROVACAO_NOTA + 0.1, 'Média mínima para aprovação',
|
356 |
-
transform=plt.gca().get_yaxis_transform(), color='r', alpha=0.5)
|
357 |
-
|
358 |
-
# Ajustar legenda
|
359 |
-
if n_disciplinas > 8:
|
360 |
-
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8,
|
361 |
-
ncol=max(1, n_disciplinas // 12))
|
362 |
-
else:
|
363 |
-
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', ncol=1)
|
364 |
-
|
365 |
plt.tight_layout()
|
366 |
|
367 |
-
# Usar nome de arquivo personalizado se fornecido
|
368 |
nome_arquivo = nome_arquivo or 'evolucao_notas.png'
|
369 |
plot_path = os.path.join(temp_dir, nome_arquivo)
|
370 |
plt.savefig(plot_path, bbox_inches='tight', dpi=300)
|
371 |
plt.close()
|
372 |
return plot_path
|
373 |
-
|
374 |
def plotar_graficos_destacados(disciplinas_dados, temp_dir):
|
375 |
-
"""Plota gráficos de médias e frequências com destaques."""
|
376 |
n_disciplinas = len(disciplinas_dados)
|
377 |
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
# Criar figura
|
382 |
-
plt.figure(figsize=(12, 10))
|
383 |
|
384 |
disciplinas = [d['disciplina'] for d in disciplinas_dados]
|
385 |
medias_notas = [d['media_notas'] for d in disciplinas_dados]
|
386 |
medias_freq = [d['media_freq'] for d in disciplinas_dados]
|
387 |
|
388 |
-
#
|
389 |
-
|
390 |
-
plt.subplots_adjust(hspace=0.5) # Aumentar espaço entre os gráficos
|
391 |
-
|
392 |
-
# Definir cores baseadas nos limites de aprovação
|
393 |
-
cores_notas = ['red' if media < LIMITE_APROVACAO_NOTA else '#2ecc71' for media in medias_notas]
|
394 |
-
cores_freq = ['red' if media < LIMITE_APROVACAO_FREQ else '#2ecc71' for media in medias_freq]
|
395 |
|
396 |
-
# Calcular médias globais
|
397 |
media_global = np.mean(medias_notas)
|
398 |
freq_global = np.mean(medias_freq)
|
399 |
|
400 |
-
|
401 |
-
|
402 |
-
ax1.set_title('Média de Notas por Disciplina', pad=20, fontsize=12, fontweight='bold')
|
403 |
ax1.set_ylim(0, ESCALA_MAXIMA_NOTAS)
|
404 |
-
ax1.grid(True, axis='y', alpha=0.3, linestyle='--')
|
405 |
-
|
406 |
-
# Melhorar a apresentação dos rótulos
|
407 |
-
ax1.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
|
408 |
-
ax1.set_ylabel('Notas', fontsize=10, labelpad=10)
|
409 |
-
|
410 |
-
# Adicionar linha de média mínima
|
411 |
ax1.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
|
412 |
-
ax1.
|
413 |
-
|
414 |
-
|
415 |
-
# Valores nas barras de notas
|
416 |
-
for barra in barras_notas:
|
417 |
-
altura = barra.get_height()
|
418 |
-
ax1.text(barra.get_x() + barra.get_width()/2., altura,
|
419 |
-
f'{altura:.1f}',
|
420 |
-
ha='center', va='bottom', fontsize=8)
|
421 |
-
|
422 |
-
# Gráfico de frequências
|
423 |
-
barras_freq = ax2.bar(disciplinas, medias_freq, color=cores_freq)
|
424 |
-
ax2.set_title('Frequência Média por Disciplina', pad=20, fontsize=12, fontweight='bold')
|
425 |
-
ax2.set_ylim(0, 110)
|
426 |
-
ax2.grid(True, axis='y', alpha=0.3, linestyle='--')
|
427 |
|
428 |
-
|
429 |
-
|
430 |
-
ax2.set_ylabel('Frequência (%)', fontsize=10, labelpad=10)
|
431 |
|
432 |
-
|
|
|
|
|
433 |
ax2.axhline(y=LIMITE_APROVACAO_FREQ, color='r', linestyle='--', alpha=0.3)
|
434 |
-
ax2.
|
435 |
-
|
436 |
-
|
437 |
-
# Valores nas barras de frequência
|
438 |
-
for barra in barras_freq:
|
439 |
-
altura = barra.get_height()
|
440 |
-
ax2.text(barra.get_x() + barra.get_width()/2., altura,
|
441 |
-
f'{altura:.1f}%',
|
442 |
-
ha='center', va='bottom', fontsize=8)
|
443 |
-
|
444 |
-
# Título global com informações de média
|
445 |
-
plt.suptitle(
|
446 |
-
f'Desempenho Geral\nMédia Global: {media_global:.1f} | Frequência Global: {freq_global:.1f}%',
|
447 |
-
y=0.98, fontsize=14, fontweight='bold'
|
448 |
-
)
|
449 |
-
|
450 |
-
# Aviso de risco de reprovação se necessário
|
451 |
-
if freq_global < LIMITE_APROVACAO_FREQ:
|
452 |
-
plt.figtext(0.5, 0.02,
|
453 |
-
"Atenção: Risco de Reprovação por Baixa Frequência",
|
454 |
-
ha="center", fontsize=11, color="red", weight='bold')
|
455 |
|
456 |
-
|
|
|
457 |
|
458 |
-
|
459 |
plot_path = os.path.join(temp_dir, 'medias_frequencias.png')
|
460 |
plt.savefig(plot_path, bbox_inches='tight', dpi=300)
|
461 |
plt.close()
|
|
|
248 |
return cores_base[:n_cores]
|
249 |
|
250 |
def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arquivo=None):
|
|
|
251 |
n_disciplinas = len(disciplinas_dados)
|
252 |
|
|
|
|
|
|
|
253 |
plt.figure(figsize=(11.69, 8.27))
|
|
|
254 |
cores = gerar_paleta_cores(n_disciplinas)
|
255 |
+
marcadores = ['o', 's', '^', 'D', 'v', '<', '>', 'p', '*', 'h']
|
256 |
+
estilos_linha = ['-', '--', '-.', ':'] * (n_disciplinas // 4 + 1)
|
257 |
|
|
|
|
|
|
|
258 |
deslocamentos = np.linspace(-0.03, 0.03, n_disciplinas)
|
259 |
+
anotacoes_usadas = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
|
|
|
261 |
for idx, disc_data in enumerate(disciplinas_dados):
|
262 |
notas = pd.Series(disc_data['notas'])
|
263 |
bimestres_cursados = disc_data['bimestres_cursados']
|
|
|
269 |
bimestres_deslocados = [bim + desloc for bim in bimestres]
|
270 |
|
271 |
if notas_validas:
|
|
|
272 |
plt.plot(bimestres_deslocados, notas_validas,
|
273 |
+
color=cores[idx % len(cores)],
|
274 |
+
marker=marcadores[idx % len(marcadores)],
|
275 |
+
markersize=8,
|
276 |
+
linewidth=2,
|
277 |
+
label=disc_data['disciplina'],
|
278 |
+
linestyle=estilos_linha[idx % len(estilos_linha)])
|
|
|
279 |
|
280 |
+
for bim, nota in zip(bimestres_deslocados, notas_validas):
|
281 |
+
y_offset = 10
|
282 |
+
while any(abs(y - (nota + y_offset/20)) < 0.4 for y, _ in anotacoes_usadas.get(bim, [])):
|
283 |
+
y_offset += 5
|
284 |
+
|
285 |
+
plt.annotate(f"{nota:.1f}", (bim, nota), xytext=(0, y_offset),
|
286 |
+
textcoords="offset points", ha='center', va='bottom', fontsize=9,
|
287 |
+
bbox=dict(facecolor='white', edgecolor=cores[idx % len(cores)], alpha=0.8, pad=2))
|
288 |
+
anotacoes_usadas.setdefault(bim, []).append((nota + y_offset/20, nota))
|
289 |
+
|
290 |
+
plt.title(titulo or 'Evolução das Médias por Disciplina', pad=20, fontsize=14, fontweight='bold')
|
291 |
+
plt.xlabel('Bimestres', fontsize=12, labelpad=10)
|
292 |
+
plt.ylabel('Notas', fontsize=12, labelpad=10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
plt.xticks([1, 2, 3, 4], ['1º Bim', '2º Bim', '3º Bim', '4º Bim'])
|
294 |
plt.ylim(0, ESCALA_MAXIMA_NOTAS)
|
295 |
+
plt.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
296 |
plt.tight_layout()
|
297 |
|
|
|
298 |
nome_arquivo = nome_arquivo or 'evolucao_notas.png'
|
299 |
plot_path = os.path.join(temp_dir, nome_arquivo)
|
300 |
plt.savefig(plot_path, bbox_inches='tight', dpi=300)
|
301 |
plt.close()
|
302 |
return plot_path
|
303 |
+
|
304 |
def plotar_graficos_destacados(disciplinas_dados, temp_dir):
|
|
|
305 |
n_disciplinas = len(disciplinas_dados)
|
306 |
|
307 |
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[1, 1])
|
308 |
+
plt.subplots_adjust(hspace=0.5)
|
|
|
|
|
|
|
309 |
|
310 |
disciplinas = [d['disciplina'] for d in disciplinas_dados]
|
311 |
medias_notas = [d['media_notas'] for d in disciplinas_dados]
|
312 |
medias_freq = [d['media_freq'] for d in disciplinas_dados]
|
313 |
|
314 |
+
cores_notas = ['#E74C3C' if media < LIMITE_APROVACAO_NOTA else '#2ECC71' for media in medias_notas]
|
315 |
+
cores_freq = ['#E74C3C' if media < LIMITE_APROVACAO_FREQ else '#2ECC71' for media in medias_freq]
|
|
|
|
|
|
|
|
|
|
|
316 |
|
|
|
317 |
media_global = np.mean(medias_notas)
|
318 |
freq_global = np.mean(medias_freq)
|
319 |
|
320 |
+
ax1.bar(disciplinas, medias_notas, color=cores_notas)
|
321 |
+
ax1.set_title('Média de Notas por Disciplina', fontsize=14, fontweight='bold')
|
|
|
322 |
ax1.set_ylim(0, ESCALA_MAXIMA_NOTAS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
ax1.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
|
324 |
+
ax1.set_xticklabels(disciplinas, rotation=45, ha='right', va='top', fontsize=10)
|
325 |
+
ax1.set_ylabel('Notas', fontsize=12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
|
327 |
+
for idx, (disc, media) in enumerate(zip(disciplinas, medias_notas)):
|
328 |
+
ax1.text(idx, media + 0.2, f'{media:.1f}', ha='center', color='black')
|
|
|
329 |
|
330 |
+
ax2.bar(disciplinas, medias_freq, color=cores_freq)
|
331 |
+
ax2.set_title('Frequência Média por Disciplina', fontsize=14, fontweight='bold')
|
332 |
+
ax2.set_ylim(0, 110)
|
333 |
ax2.axhline(y=LIMITE_APROVACAO_FREQ, color='r', linestyle='--', alpha=0.3)
|
334 |
+
ax2.set_xticklabels(disciplinas, rotation=45, ha='right', va='top', fontsize=10)
|
335 |
+
ax2.set_ylabel('Frequência (%)', fontsize=12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
336 |
|
337 |
+
for idx, (disc, freq) in enumerate(zip(disciplinas, medias_freq)):
|
338 |
+
ax2.text(idx, freq + 1, f'{freq:.1f}%', ha='center', color='black')
|
339 |
|
340 |
+
plt.tight_layout()
|
341 |
plot_path = os.path.join(temp_dir, 'medias_frequencias.png')
|
342 |
plt.savefig(plot_path, bbox_inches='tight', dpi=300)
|
343 |
plt.close()
|