230 lines
6.7 KiB
Python
230 lines
6.7 KiB
Python
from PyQt5.QtCore import Qt, QTimer
|
||
from PyQt5.QtWidgets import (
|
||
QDialog,
|
||
QVBoxLayout,
|
||
QScrollArea,
|
||
QWidget,
|
||
QPushButton,
|
||
QLineEdit,
|
||
QMessageBox,
|
||
QHBoxLayout,
|
||
QCheckBox,
|
||
)
|
||
|
||
import numpy as np
|
||
|
||
from .button_group import ButtonGroup
|
||
from .graph_requester import GraphRequester
|
||
|
||
from parser import Parser
|
||
|
||
from graph_widget import GraphWidget
|
||
|
||
|
||
class PlotterDialog(QDialog):
|
||
focused_line_edit = None
|
||
|
||
def __init__(
|
||
self,
|
||
variable_full_names: dict[str, str],
|
||
function_full_names: dict[str, str],
|
||
variable_values: dict[str, np.ndarray],
|
||
):
|
||
super().__init__()
|
||
|
||
self.variable_values = variable_values
|
||
|
||
self.setWindowTitle("Графопостроитель")
|
||
|
||
layout_boss = QVBoxLayout() # главный лояут
|
||
|
||
self.scroll = QScrollArea()
|
||
|
||
self.scroll.verticalScrollBar().rangeChanged.connect(self.scroll_bottom)
|
||
|
||
scrollWidget = QWidget()
|
||
|
||
# self.scroll.setFrameShape(QFrame.NoFrame)
|
||
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||
|
||
self.inputs_layout = QVBoxLayout() # лаяут первой трети
|
||
self.inputs_layout.addStretch(1)
|
||
|
||
self.num_of_input = 0 # инициализация первого графика
|
||
self.add_input()
|
||
|
||
QTimer.singleShot(0, self.focus_first_input)
|
||
|
||
scrollWidget.setLayout(self.inputs_layout)
|
||
|
||
self.scroll.setWidgetResizable(True)
|
||
self.scroll.setWidget(scrollWidget)
|
||
|
||
layout_boss.addWidget(self.scroll)
|
||
|
||
Button_make_fun_button = QPushButton("+")
|
||
Button_make_fun_button.clicked.connect(self.add_input)
|
||
Button_make_fun_button.setFixedWidth(80)
|
||
layout_boss.addWidget(Button_make_fun_button, alignment=Qt.AlignRight)
|
||
|
||
layout_boss.addWidget(
|
||
ButtonGroup(
|
||
"Переменные",
|
||
full_names=variable_full_names,
|
||
buttons_action=self.insert_variable,
|
||
)
|
||
)
|
||
layout_boss.addWidget(
|
||
ButtonGroup(
|
||
"Функции",
|
||
full_names=function_full_names,
|
||
buttons_action=self.insert_function,
|
||
)
|
||
)
|
||
|
||
layout_boss.addSpacing(10)
|
||
|
||
buttons_layout = QHBoxLayout()
|
||
|
||
buttons_layout.setDirection(QHBoxLayout.RightToLeft)
|
||
|
||
submit_button = QPushButton("Построить")
|
||
reset_button = QPushButton("Сброс")
|
||
|
||
submit_button.clicked.connect(self.plot)
|
||
reset_button.clicked.connect(self.reset)
|
||
|
||
submit_button.setDefault(True)
|
||
|
||
buttons_layout.addWidget(submit_button)
|
||
buttons_layout.addWidget(reset_button)
|
||
|
||
self.subplots_checkbox = QCheckBox("Рисовать графики на отдельных осях")
|
||
buttons_layout.addWidget(self.subplots_checkbox)
|
||
|
||
buttons_layout.addStretch(1)
|
||
|
||
layout_boss.addLayout(buttons_layout)
|
||
|
||
self.setLayout(layout_boss)
|
||
|
||
def add_input(self):
|
||
self.num_of_input += 1
|
||
|
||
graph_requester = GraphRequester(self.num_of_input)
|
||
|
||
for line_edit in (graph_requester.LineEditX, graph_requester.LineEditY):
|
||
line_edit.focused_in.connect(
|
||
lambda line_edit=line_edit: self.set_focused_line_edit(line_edit)
|
||
)
|
||
|
||
graph_requester.LineEditGraf.focused_in.connect(
|
||
lambda: self.set_focused_line_edit(None)
|
||
)
|
||
|
||
self.inputs_layout.insertWidget(self.inputs_layout.count() - 1, graph_requester)
|
||
|
||
graph_requester.LineEditGraf.setFocus()
|
||
|
||
def set_focused_line_edit(self, line_edit: QLineEdit | None):
|
||
self.focused_line_edit = line_edit
|
||
|
||
def scroll_bottom(self):
|
||
self.scroll.verticalScrollBar().setValue(
|
||
self.scroll.verticalScrollBar().maximum()
|
||
)
|
||
|
||
def insert_string(self, string: str, string_cursor_padding=-1):
|
||
line_edit = self.focused_line_edit
|
||
|
||
if line_edit is None:
|
||
dlg = QMessageBox(
|
||
QMessageBox.Warning,
|
||
"Ошибка",
|
||
"Выберите поле ввода выражения",
|
||
)
|
||
dlg.exec()
|
||
|
||
return
|
||
|
||
cusor_pos = line_edit.cursorPosition()
|
||
text = line_edit.text()
|
||
|
||
if string_cursor_padding < 1:
|
||
string_cursor_padding = len(string)
|
||
|
||
line_edit.setText(text[:cusor_pos] + string + text[cusor_pos:])
|
||
line_edit.setCursorPosition(cusor_pos + string_cursor_padding)
|
||
|
||
self.scroll.ensureWidgetVisible(line_edit)
|
||
line_edit.setFocus()
|
||
|
||
def insert_variable(self, name: str):
|
||
self.insert_string(f" {name} ")
|
||
|
||
def insert_function(self, name: str):
|
||
string = f" {name}()"
|
||
self.insert_string(string, len(string) - 1) # len - 1 for cursor between braces
|
||
|
||
def plot(self):
|
||
xs, ys, labels = [], [], []
|
||
|
||
for i in range(self.inputs_layout.count()):
|
||
graph_requester = self.inputs_layout.itemAt(i).widget()
|
||
|
||
if graph_requester is not None:
|
||
x_expr = graph_requester.LineEditX.text()
|
||
y_expr = graph_requester.LineEditY.text()
|
||
label = graph_requester.LineEditGraf.text()
|
||
|
||
if len(x_expr) * len(y_expr) == 0:
|
||
dlg = QMessageBox(
|
||
QMessageBox.Warning,
|
||
"Ошибка",
|
||
f'График "{label}" не задан',
|
||
)
|
||
dlg.exec()
|
||
return
|
||
|
||
x = Parser(x_expr).evaluate(self.variable_values)
|
||
y = Parser(y_expr).evaluate(self.variable_values)
|
||
|
||
xs.append(x)
|
||
ys.append(y)
|
||
labels.append(label)
|
||
|
||
mult_subplots = self.subplots_checkbox.isChecked()
|
||
|
||
self.graph = GraphWidget(xs, ys, labels, mult_plots=mult_subplots)
|
||
|
||
self.graph.show()
|
||
|
||
def reset(self):
|
||
dlg = QMessageBox(
|
||
QMessageBox.Question,
|
||
"Очистка",
|
||
"Вы уверены, что хотите очистить все введённые выражения?",
|
||
buttons=QMessageBox.Yes | QMessageBox.No,
|
||
)
|
||
|
||
res = dlg.exec()
|
||
|
||
if res != QMessageBox.Yes:
|
||
return
|
||
|
||
while self.inputs_layout.count() > 0:
|
||
widget = self.inputs_layout.takeAt(0).widget()
|
||
if widget is not None:
|
||
widget.setParent(None)
|
||
|
||
self.focused_line_edit = None
|
||
|
||
self.num_of_input = 0
|
||
|
||
self.add_input()
|
||
|
||
def focus_first_input(self):
|
||
first_graph_request = self.inputs_layout.itemAt(0).widget()
|
||
if first_graph_request is not None:
|
||
first_graph_request.LineEditGraf.setFocus()
|