Carregar ficheiros para "/"
This commit is contained in:
492
binary_graph.py
Normal file
492
binary_graph.py
Normal file
@ -0,0 +1,492 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
import numpy as np
|
||||
from logger import logger
|
||||
|
||||
class BinaryGraphWindow:
|
||||
def __init__(self, parent, binary_data):
|
||||
try:
|
||||
logger.info("Initializing Binary Graph Window")
|
||||
|
||||
# Validate input
|
||||
if not isinstance(binary_data, (list, np.ndarray)) or len(binary_data) == 0:
|
||||
raise ValueError("Invalid binary data: must be a non-empty list or numpy array")
|
||||
|
||||
# Store data as instance attribute
|
||||
self.binary_data = list(binary_data)
|
||||
self.parent = parent
|
||||
|
||||
# Criar janela
|
||||
self.window = tk.Toplevel(parent)
|
||||
self.window.title("2D Binary Graph")
|
||||
self.window.geometry("1000x600")
|
||||
|
||||
# Frame principal
|
||||
self.main_frame = ttk.Frame(self.window)
|
||||
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
# Frame para os botões de bits e configurações
|
||||
self.top_frame = ttk.Frame(self.main_frame)
|
||||
self.top_frame.pack(fill=tk.X, padx=5, pady=5)
|
||||
|
||||
# Frame para os botões de bits
|
||||
self.bits_frame = ttk.Frame(self.top_frame)
|
||||
self.bits_frame.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
# Frame para seleção de pontos
|
||||
self.points_frame = ttk.Frame(self.top_frame)
|
||||
self.points_frame.pack(side=tk.LEFT)
|
||||
|
||||
# Variável para controlar o modo de bits
|
||||
self.bits_mode = tk.StringVar(value="8b")
|
||||
|
||||
# Variável para controlar o número de pontos
|
||||
self.points_var = tk.IntVar(value=512)
|
||||
|
||||
# Estilo para botões pequenos
|
||||
style = ttk.Style()
|
||||
style.configure("Small.TButton", padding=1)
|
||||
|
||||
# Botões para seleção de bits
|
||||
self.btn_8b = ttk.Button(self.bits_frame, text="8b", width=3,
|
||||
style="Small.TButton",
|
||||
command=lambda: self.change_bits_mode("8b"))
|
||||
self.btn_8b.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self.btn_16b = ttk.Button(self.bits_frame, text="16b", width=3,
|
||||
style="Small.TButton",
|
||||
command=lambda: self.change_bits_mode("16b"))
|
||||
self.btn_16b.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self.btn_32b = ttk.Button(self.bits_frame, text="32b", width=3,
|
||||
style="Small.TButton",
|
||||
command=lambda: self.change_bits_mode("32b"))
|
||||
self.btn_32b.pack(side=tk.LEFT, padx=2)
|
||||
|
||||
# Label e Spinbox para seleção de pontos
|
||||
ttk.Label(self.points_frame, text="Points:").pack(side=tk.LEFT, padx=(0, 5))
|
||||
self.points_spinbox = ttk.Spinbox(
|
||||
self.points_frame,
|
||||
from_=5,
|
||||
to=1500,
|
||||
textvariable=self.points_var,
|
||||
width=5,
|
||||
command=self.update_points_display
|
||||
)
|
||||
self.points_spinbox.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
# Frame para canvas e scrollbar
|
||||
self.graph_frame = ttk.Frame(self.main_frame)
|
||||
self.graph_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Criar label para hover
|
||||
self.hover_label = tk.Label(self.window, text="", bg='lightyellow', relief=tk.SOLID, borderwidth=1)
|
||||
|
||||
# Canvas para o gráfico
|
||||
self.canvas = tk.Canvas(self.graph_frame, bg='white')
|
||||
self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Scrollbar horizontal
|
||||
self.scrollbar = ttk.Scrollbar(self.graph_frame,
|
||||
orient=tk.HORIZONTAL,
|
||||
command=self.on_scroll)
|
||||
self.scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
# Configurar canvas para usar scrollbar
|
||||
self.canvas.configure(xscrollcommand=self.scrollbar.set)
|
||||
|
||||
# Configurar eventos de mouse
|
||||
self.canvas.bind('<Motion>', self.on_hover)
|
||||
self.canvas.bind('<Leave>', self.hide_hover_label)
|
||||
|
||||
# Configurações iniciais
|
||||
self.max_display = 512 # Definir 512 pontos como padrão
|
||||
self.current_start = 0
|
||||
self.points_var.set(512) # Definir valor inicial do spinbox
|
||||
|
||||
# Processar dados iniciais
|
||||
self.process_data()
|
||||
|
||||
# Plotar dados iniciais
|
||||
self.plot_data()
|
||||
|
||||
# Destacar botão 8b inicialmente
|
||||
self.update_bits_buttons()
|
||||
|
||||
# Vincular evento de redimensionamento
|
||||
self.canvas.bind('<Configure>', self.on_resize)
|
||||
|
||||
# Configurar scrolling com o mouse
|
||||
self.canvas.bind('<MouseWheel>', self.on_mousewheel)
|
||||
|
||||
logger.info(f"Binary Graph initialized with {len(binary_data)} data points")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing Binary Graph: {e}", exc_info=True)
|
||||
messagebox.showerror("Initialization Error", str(e))
|
||||
if hasattr(self, 'window'):
|
||||
self.window.destroy()
|
||||
|
||||
def on_scroll(self, *args):
|
||||
"""Manipula a rolagem do scrollbar"""
|
||||
try:
|
||||
# Obter a posição atual do scrollbar
|
||||
if len(args) > 1: # Scroll através da scrollbar
|
||||
fraction = float(args[1])
|
||||
total_points = len(self.processed_data)
|
||||
|
||||
# Calcular novo índice inicial baseado na fração da scrollbar
|
||||
max_start = max(0, total_points - self.max_display)
|
||||
self.current_start = int(fraction * max_start)
|
||||
|
||||
# Garantir que current_start não ultrapasse o limite
|
||||
self.current_start = max(0, min(self.current_start, max_start))
|
||||
|
||||
# Replottar com a nova posição
|
||||
self.plot_data()
|
||||
|
||||
logger.info(f"Scrolled to position {self.current_start}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in scroll handling: {e}", exc_info=True)
|
||||
|
||||
def update_scrollbar(self):
|
||||
"""Atualiza a posição e o tamanho do scrollbar"""
|
||||
try:
|
||||
total_points = len(self.processed_data)
|
||||
|
||||
if total_points <= self.max_display:
|
||||
# Se todos os pontos estão visíveis, desabilitar scrollbar
|
||||
self.scrollbar.set(0, 1)
|
||||
return
|
||||
|
||||
# Calcular a fração visível
|
||||
visible_fraction = self.max_display / total_points
|
||||
|
||||
# Calcular a posição inicial como fração
|
||||
max_start = total_points - self.max_display
|
||||
start_fraction = self.current_start / max_start if max_start > 0 else 0
|
||||
|
||||
# Definir posição e tamanho do scrollbar
|
||||
self.scrollbar.set(start_fraction, start_fraction + visible_fraction)
|
||||
|
||||
logger.info(f"Updated scrollbar: position {start_fraction:.2f}, size {visible_fraction:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating scrollbar: {e}", exc_info=True)
|
||||
|
||||
def change_bits_mode(self, mode):
|
||||
# Definir o modo de bits
|
||||
self.bits_mode.set(mode)
|
||||
|
||||
# Atualizar estado dos botões
|
||||
self.update_bits_buttons()
|
||||
|
||||
# Processar dados com o novo modo de bits
|
||||
self.process_data()
|
||||
|
||||
# Resetar para 512 pontos ao mudar o modo de bits
|
||||
self.points_var.set(512)
|
||||
self.max_display = 512
|
||||
self.current_start = 0
|
||||
|
||||
# Replottar dados
|
||||
self.plot_data()
|
||||
logger.info(f"Changed graph mode to {mode}")
|
||||
|
||||
def update_bits_buttons(self):
|
||||
# Atualizar aparência dos botões
|
||||
current_mode = self.bits_mode.get()
|
||||
for btn, mode in [(self.btn_8b, "8b"),
|
||||
(self.btn_16b, "16b"),
|
||||
(self.btn_32b, "32b")]:
|
||||
if mode == current_mode:
|
||||
btn.state(['pressed'])
|
||||
else:
|
||||
btn.state(['!pressed'])
|
||||
|
||||
def process_data(self):
|
||||
# Processar dados de acordo com o modo selecionado
|
||||
mode = self.bits_mode.get()
|
||||
raw_data = self.binary_data
|
||||
|
||||
if mode == "8b":
|
||||
self.processed_data = raw_data
|
||||
elif mode == "16b":
|
||||
# Converter para 16 bits (combinar bytes adjacentes)
|
||||
self.processed_data = []
|
||||
for i in range(0, len(raw_data)-1, 2):
|
||||
value = (raw_data[i+1] << 8) | raw_data[i]
|
||||
self.processed_data.append(value)
|
||||
elif mode == "32b":
|
||||
# Converter para 32 bits (combinar 4 bytes adjacentes)
|
||||
self.processed_data = []
|
||||
for i in range(0, len(raw_data)-3, 4):
|
||||
value = (raw_data[i+3] << 24) | (raw_data[i+2] << 16) | \
|
||||
(raw_data[i+1] << 8) | raw_data[i]
|
||||
self.processed_data.append(value)
|
||||
|
||||
def get_max_value_for_mode(self):
|
||||
"""Retorna o valor máximo para o modo de bits atual"""
|
||||
mode = self.bits_mode.get()
|
||||
if mode == "8b":
|
||||
return 255
|
||||
elif mode == "16b":
|
||||
return 65535
|
||||
else: # 32b
|
||||
return 4294967295
|
||||
|
||||
def format_hex(self, value):
|
||||
"""Formata um número para hexadecimal sem o prefixo 0x"""
|
||||
return format(value, 'X')
|
||||
|
||||
def plot_data(self):
|
||||
"""Plota os dados no canvas"""
|
||||
try:
|
||||
# Limpar canvas
|
||||
self.canvas.delete("all")
|
||||
|
||||
# Obter dimensões do canvas
|
||||
plot_width = self.canvas.winfo_width() - 100 # Margem para eixos
|
||||
plot_height = self.canvas.winfo_height() - 100 # Margem para eixos
|
||||
margin_left = 50
|
||||
margin_bottom = 50
|
||||
|
||||
if not self.processed_data or len(self.processed_data) == 0:
|
||||
return
|
||||
|
||||
# Calcular índices de início e fim para exibição
|
||||
end_idx = min(self.current_start + self.max_display, len(self.processed_data))
|
||||
visible_data = self.processed_data[self.current_start:end_idx]
|
||||
|
||||
if not visible_data:
|
||||
return
|
||||
|
||||
# Usar valor máximo fixo baseado no modo de bits
|
||||
max_val = self.get_max_value_for_mode()
|
||||
min_val = 0 # Sempre começar do zero
|
||||
|
||||
# Calcular escalas
|
||||
x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width
|
||||
y_scale = plot_height / max_val if max_val != 0 else 1
|
||||
|
||||
# Preparar pontos para plotagem
|
||||
points = []
|
||||
for i, value in enumerate(visible_data):
|
||||
x = margin_left + (i * x_scale)
|
||||
y = self.canvas.winfo_height() - margin_bottom - (value * y_scale)
|
||||
points.extend([x, y])
|
||||
|
||||
# Plotar linha
|
||||
if len(points) >= 4:
|
||||
self.canvas.create_line(points, fill='blue', width=1)
|
||||
|
||||
# Desenhar eixo X
|
||||
self.canvas.create_line(
|
||||
margin_left, self.canvas.winfo_height() - margin_bottom,
|
||||
margin_left + plot_width, self.canvas.winfo_height() - margin_bottom,
|
||||
fill='black'
|
||||
)
|
||||
|
||||
# Desenhar eixo Y
|
||||
self.canvas.create_line(
|
||||
margin_left, self.canvas.winfo_height() - margin_bottom,
|
||||
margin_left, margin_bottom,
|
||||
fill='black'
|
||||
)
|
||||
|
||||
# Desenhar marcações no eixo X
|
||||
num_x_ticks = 10
|
||||
for i in range(num_x_ticks + 1):
|
||||
x_pos = margin_left + (i * plot_width / num_x_ticks)
|
||||
self.canvas.create_line(
|
||||
x_pos, self.canvas.winfo_height() - margin_bottom,
|
||||
x_pos, self.canvas.winfo_height() - margin_bottom + 5,
|
||||
fill='black'
|
||||
)
|
||||
x_val = self.current_start + int((i / num_x_ticks) * (end_idx - self.current_start))
|
||||
|
||||
# Multiplicar por 2 e converter para hex apenas no modo 16 bits
|
||||
if self.bits_mode.get() == "16b":
|
||||
display_val = x_val * 2
|
||||
hex_x_val = self.format_hex(display_val)
|
||||
else:
|
||||
hex_x_val = self.format_hex(x_val)
|
||||
|
||||
self.canvas.create_text(
|
||||
x_pos, self.canvas.winfo_height() - margin_bottom + 20,
|
||||
text=hex_x_val
|
||||
)
|
||||
|
||||
# Desenhar marcações no eixo Y
|
||||
num_y_ticks = 5
|
||||
for i in range(num_y_ticks + 1):
|
||||
y_val = int((i / num_y_ticks) * max_val)
|
||||
y_pos = self.canvas.winfo_height() - margin_bottom - (y_val * y_scale)
|
||||
self.canvas.create_line(
|
||||
margin_left - 5, y_pos,
|
||||
margin_left, y_pos,
|
||||
fill='black'
|
||||
)
|
||||
# Manter valor do eixo Y em decimal
|
||||
self.canvas.create_text(
|
||||
margin_left - 25, y_pos,
|
||||
text=str(y_val)
|
||||
)
|
||||
|
||||
# Atualizar scrollbar
|
||||
self.update_scrollbar()
|
||||
|
||||
# Atualizar título do gráfico
|
||||
title = f"{self.bits_mode.get()}-bit Binary Data"
|
||||
self.canvas.create_text(
|
||||
self.canvas.winfo_width() // 2,
|
||||
20,
|
||||
text=title,
|
||||
font=('Arial', 12, 'bold')
|
||||
)
|
||||
|
||||
logger.info(f"Plotted {len(visible_data)} points from index {self.current_start} to {end_idx}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error plotting data: {e}", exc_info=True)
|
||||
messagebox.showerror("Plotting Error", str(e))
|
||||
|
||||
def on_resize(self, event):
|
||||
# Replottar quando o canvas for redimensionado
|
||||
self.plot_data()
|
||||
|
||||
def update_points_display(self):
|
||||
"""Atualiza o número de pontos exibidos no gráfico"""
|
||||
try:
|
||||
# Obter o novo número de pontos do spinbox
|
||||
new_points = self.points_var.get()
|
||||
|
||||
# Validar entrada
|
||||
if new_points < 5 or new_points > 1500:
|
||||
messagebox.showerror("Invalid Input", "Points must be between 5 and 1500")
|
||||
# Restaurar o valor anterior
|
||||
self.points_var.set(self.max_display)
|
||||
return
|
||||
|
||||
# Limpar canvas antes de replottar
|
||||
self.canvas.delete("all")
|
||||
|
||||
# Atualizar número máximo de pontos
|
||||
self.max_display = new_points
|
||||
|
||||
# Garantir que o início atual não ultrapasse o total de pontos
|
||||
total_points = len(self.processed_data)
|
||||
self.current_start = min(self.current_start, max(0, total_points - new_points))
|
||||
|
||||
# Replottar dados
|
||||
self.plot_data()
|
||||
|
||||
# Atualizar scrollbar
|
||||
self.update_scrollbar()
|
||||
|
||||
logger.info(f"Updated graph display to {new_points} points")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating points display: {e}", exc_info=True)
|
||||
messagebox.showerror("Update Error", str(e))
|
||||
|
||||
def on_mousewheel(self, event):
|
||||
# Manipula a rolagem com o mouse
|
||||
try:
|
||||
# Obter a posição atual do scrollbar
|
||||
total_points = len(self.processed_data)
|
||||
|
||||
# Calcular novo índice inicial baseado na fração da scrollbar
|
||||
max_start = max(0, total_points - self.max_display)
|
||||
if event.delta > 0:
|
||||
self.current_start = max(0, self.current_start - 10)
|
||||
else:
|
||||
self.current_start = min(max_start, self.current_start + 10)
|
||||
|
||||
# Replottar com a nova posição
|
||||
self.plot_data()
|
||||
|
||||
logger.info(f"Scrolled to position {self.current_start}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in mousewheel handling: {e}", exc_info=True)
|
||||
|
||||
def on_hover(self, event):
|
||||
try:
|
||||
# Parâmetros de plotagem
|
||||
width = self.canvas.winfo_width()
|
||||
height = self.canvas.winfo_height()
|
||||
margin_left = 50
|
||||
margin_bottom = 50
|
||||
plot_width = width - 100
|
||||
plot_height = height - 100
|
||||
|
||||
# Remover linhas de hover anteriores
|
||||
self.canvas.delete('hover_line', 'hover_marker')
|
||||
|
||||
# Verificar se o mouse está dentro da área do gráfico
|
||||
if (event.x < margin_left or event.x > width - 50 or
|
||||
event.y < 50 or event.y > height - margin_bottom):
|
||||
self.hide_hover_label(event)
|
||||
return
|
||||
|
||||
# Dados processados
|
||||
data = self.processed_data
|
||||
end_idx = min(self.current_start + self.max_display, len(data))
|
||||
visible_data = data[self.current_start:end_idx]
|
||||
|
||||
if not visible_data:
|
||||
return
|
||||
|
||||
# Calcular índice X baseado na posição relativa do mouse
|
||||
mouse_x_rel = event.x - margin_left
|
||||
x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width
|
||||
x_index = int(mouse_x_rel / x_scale)
|
||||
|
||||
if x_index < 0 or x_index >= len(visible_data):
|
||||
return
|
||||
|
||||
# Calcular valor Y
|
||||
y_value = visible_data[x_index]
|
||||
x_pos = self.current_start + x_index
|
||||
|
||||
# Calcular coordenadas para linha vertical
|
||||
line_x = margin_left + (x_index * x_scale)
|
||||
|
||||
# Desenhar linha vertical
|
||||
self.canvas.create_line(
|
||||
line_x, 50,
|
||||
line_x, height - margin_bottom,
|
||||
fill='red',
|
||||
dash=(4, 4),
|
||||
tags='hover_line'
|
||||
)
|
||||
|
||||
# Formatar valores
|
||||
hex_x = format(x_pos * 2, 'X') if self.bits_mode.get() == "16b" else format(x_pos, 'X')
|
||||
hex_y = format(y_value, 'X')
|
||||
|
||||
# Atualizar label de hover
|
||||
hover_text = (
|
||||
f"X: 0x{hex_x}\n"
|
||||
f"Y: {y_value} (0x{hex_y})"
|
||||
)
|
||||
|
||||
self.hover_label.config(text=hover_text)
|
||||
|
||||
# Posicionar label próximo ao cursor
|
||||
self.hover_label.place(
|
||||
x=event.x_root - self.window.winfo_rootx() + 10,
|
||||
y=event.y_root - self.window.winfo_rooty() + 10
|
||||
)
|
||||
self.hover_label.lift()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Erro no hover: {e}")
|
||||
|
||||
def hide_hover_label(self, event):
|
||||
# Esconder label quando mouse sai do gráfico
|
||||
self.hover_label.place_forget()
|
||||
# Remover linhas de hover
|
||||
self.canvas.delete('hover_line', 'hover_marker')
|
||||
Reference in New Issue
Block a user