diff --git a/PyQt-Plotter-Dialog/README.md b/PyQt-Plotter-Dialog/README.md deleted file mode 100644 index fbc025b..0000000 --- a/PyQt-Plotter-Dialog/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# PyQT graph plotter - -## Интерфейс пакетов - -- `graph_widget` - -```python - graph = Graph(x, y, labels, mult_subplots=mult_plots) - #x,y-данные для графиков. x-вводится пользователем или подтягивается из бд - #y-формируется из введенного математического выражения - #labels- список наименований графиков - #mult_subplots=True/False отрисовка графиков в виде subplots или на одном plot - -``` -- `parser` - - [parser/README.md](./parser/README.md) - -- `plotter_dialog` - -```python -from plotter_dialog import PlotterDialog, FUNCTION_NAMES - -PlotterDialog( - variable_values: dict[str, np.ndarray] = {} # Значения для подстановки в переменные - variable_full_names: dict[str, str] = {} # Надписи для кнопок переменных и подсказок для них - function_full_names: dict[str, str] = FUNCTION_NAMES # То же самое для функций -) - -FUNCTION_NAMES = { - "abs": "Модуль", - "acos": "Арккосинус", - "acosh": "Гиперболический арккосинус", - "acot": "Арккотангенс", - "asin": "Арксинус", - "asinh": "Гиперболический арксинус", - "atan": "Арктангенс", - "avg": "Среднее", - "cos": "Косинус", - "cosh": "Гиперболический косинус", - "cot": "Котангенс", - "exp": "Экспонента (e^x)", - "lg": "Десятичный логарифм", - "ln": "Натуральный логарифм", - "log2": "Двоичный логарифм", - "max": "Максимум", - "min": "Минимум", - "prod": "Произведение", - "sgn": "Знак", - "sin": "Синус", - "sinh": "Гиперболический синус", - "sqrt": "Квадратный корень", - "sum": "Сумма", - "tanh": "Гиперболический тангенс", - "tan": "Тангенс", -} -``` - -`variable_full_names` и `variable_values` должны иметь одни и те же ключи. - -## Инструкции по запуску демо-версии - -Выполнить в корневой папке: - -```bash -python -m venv .venv -source .venv/bin/activate -pip install -r requirements.txt - -python -m plotter_dialog -``` diff --git a/PyQt-Plotter-Dialog/graph_widget/__init__.py b/PyQt-Plotter-Dialog/graph_widget/__init__.py deleted file mode 100644 index 299cfd8..0000000 --- a/PyQt-Plotter-Dialog/graph_widget/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .graph_widget import GraphWidget - -__all__ = ("GraphWidget",) diff --git a/PyQt-Plotter-Dialog/graph_widget/graph.py b/PyQt-Plotter-Dialog/graph_widget/graph.py deleted file mode 100644 index f527ebf..0000000 --- a/PyQt-Plotter-Dialog/graph_widget/graph.py +++ /dev/null @@ -1,67 +0,0 @@ -from matplotlib.figure import Figure - - -class Graph: - def __init__( - self, x, y, labels, mult_subplots=0, xl="x", yl="y", linetype="k", lims=False - ): - # словарь из настроек? - self.labels = labels - self.__x = x - self.__y = y - self.__fig = Figure(figsize=(5, 4), dpi=100) - self.__chb = mult_subplots - self.__ax = self.__fig.add_subplot() - - self.draw_n_func_plot() - - self.draw_subplots() - - self.__ax.set_xlabel(xl) - self.__ax.set_ylabel(yl) - self.__ax.legend() - self.figure.tight_layout() - - def add_vline(ax, x=0, ymin=0, ymax=1): - ax.axvline(x, ymin, ymax) - # принимаем плот и на нем же рисуем - - def add_hline( - ax, - y=0, - xmin=0, - xmax=1, - ): - ax.axhline(y, xmin, xmax) - - def draw_n_func_plot( - self, - ): # много графиков на одном холсте - if self.__chb == 0 and len(self.__x) > 1: - for i in range(len(self.__x)): - self.__ax.plot(self.__x[i], self.__y[i], label=self.labels[i]) - #новое(старое) условие против проскока - if self.__chb == 0 and len(self.__x) == 1: - self.__ax.plot(self.__x[0], self.__y[0], label=self.labels[0]) - - - - def draw_subplots( - self, - ): - if self.__chb == 1 and len(self.__x) > 1: - for i in range(len(self.__x)): - n = int(f"{len(self.__x)}1{i+1}") - - axes = self.__fig.add_subplot(n) - axes.set_title(f"График №{i+1}") - axes.plot(self.__x[i], self.__y[i], label=self.labels[i]) - #новое(старое) условие против проскока - if self.__chb ==1 and len(self.__x) == 1: - self.__ax.plot(self.__x[0], self.__y[0], label=self.labels[0]) - - @property - def figure( - self, - ): - return self.__fig diff --git a/PyQt-Plotter-Dialog/graph_widget/graph_widget.py b/PyQt-Plotter-Dialog/graph_widget/graph_widget.py deleted file mode 100644 index b6352cf..0000000 --- a/PyQt-Plotter-Dialog/graph_widget/graph_widget.py +++ /dev/null @@ -1,24 +0,0 @@ -from .graph import Graph -import matplotlib -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg -from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -from PyQt5 import QtWidgets - -matplotlib.use("Qt5Agg") - - -class GraphWidget(QtWidgets.QWidget): - def __init__(self, x, y, labels, mult_plots=False): - super().__init__() - graph = Graph(x, y, labels, mult_subplots=mult_plots) - - sc = FigureCanvasQTAgg(graph.figure) - - # Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second. - toolbar = NavigationToolbar(sc, self) - - layout = QtWidgets.QVBoxLayout() - layout.addWidget(toolbar) - layout.addWidget(sc) - - self.setLayout(layout) diff --git a/PyQt-Plotter-Dialog/parser/README.md b/PyQt-Plotter-Dialog/parser/README.md deleted file mode 100644 index b548112..0000000 --- a/PyQt-Plotter-Dialog/parser/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Math expression parser - -Math expression evaluation library. It supports most of useful math operations and functions. Expressions can contain variables which can be substituted with `int`s, `float`s or `numpy.ndarray`s. - -## Example usage - -```python -from parser import Parser - -parser = Parser("(-b + sqrt(b^2-4a c))/(2a)") - -parser.variables_names # {'c', 'a', 'b'} - -parser.evaluate({"a": 1, "b": -3, "c": 2}) # 1.0 - -parser.evaluate({"a": [1, 1, 1], "b": [-5, -6, -9], "c": [6, 9, 20]}) # [2. 3. 4.] -``` -## Expression syntax - -Expression can contain numbers or variable names with functions applied to them, separated with operators or united with braces. Numbers do not support `*`-less multiplication with spaces. Variables must be separated by space. - -Here are examples with `_` as space - -| Wrong | Right | -|--|--| -| `2_a` | `2a` or `2*a` | -| `a_2` | `a*2` | -| `a_2_a` | `a*2*a` | -| `2_cos(a)` | `2cos(a)` or `2*cos(a)` | -| `cos(a)_2` | `cos(a)*2` | -| `aa` | `a*a` or `a_a` | -| - | `cos(a)cos(a)` or `cos(a)_cos(a)` or `cos(a)*cos(a)` | - - -Theese are supported: - -Functions: - -| name | math | -|--|--| -| `abs` | $\|x\|$ | -| `acos` | $\cos^{-1}(x)$ | -| `acosh` | $\cosh^{-1}(x)$ | -| `acot` | $\cot^{-1}(x)$ | -| `asin` | $\sin^{-1}(x)$ | -| `asinh` | $\sinh^{-1}(x)$ | -| `atan` | $\tan^{-1}(x)$ | -| `avg` | $\overline X$ | -| `cos` | $\cos(x)$ | -| `cosh` | $\cosh(x)$ | -| `cot` | $\cot(x)$ | -| `exp` | $\exp(x)$ | -| `inf` | $\inf(X)$ | -| `lg` | $\lg(x)$ | -| `ln` | $\ln(x)$ | -| `log10` | $\log_{10}(x)$ | -| `log2` | $\log_2(x)$ | -| `max` | $\sup(X)$ | -| `min` | $\inf(X)$ | -| `prod` | $\displaystyle \prod_{i=0}^n x_i$ | -| `sgn` | $sgn(x)$ | -| `sign` | $sgn(x)$ | -| `sin` | $\sin(x)$ | -| `sinh` | $\sinh(x)$ | -| `sqrt` | $\sqrt{x}$ | -| `sum` | $\displaystyle\sum_{i=0}^n x_i$ | -| `sup` | $\sup(X)$ | -| `tan` | $\tan(x)$ | -| `tanh` | $\tanh(x)$ | - -Operators: `+`, `-`, `*`, `/`, `^`, `%` - -Braces: `()`, `[]`, `{}` - -Floating points: `.`, `,` - -Functions have only one argument, provided in braces. Operators must have two operands except minus (if it is the first character of equation or braced expression). - -`avg`, `sum`, `max`, `sup`, `min`, `inf` and `prod` applied on `numpy.ndarray` produce `float`. - -**! There is no error handling yet !** diff --git a/PyQt-Plotter-Dialog/parser/__init__.py b/PyQt-Plotter-Dialog/parser/__init__.py deleted file mode 100644 index 3385e94..0000000 --- a/PyQt-Plotter-Dialog/parser/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from .parser import ( - BinaryExpression, - Expression, - Operation, - Parser, - Token, - Tokenizer, - UnaryExpression, - ValueExpression, -) - -__all__ = ( - "BinaryExpression", - "Expression", - "Operation", - "Parser", - "Token", - "Tokenizer", - "UnaryExpression", - "ValueExpression", -) diff --git a/PyQt-Plotter-Dialog/parser/__main__.py b/PyQt-Plotter-Dialog/parser/__main__.py deleted file mode 100644 index 7ef68bf..0000000 --- a/PyQt-Plotter-Dialog/parser/__main__.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import Parser - -expression = input("Input math expression: ") - -parser = Parser(expression) - -print("Variables in your expression: " + ", ".join(parser.variables_names)) - -variables = {} - -for key in parser.variables_names: - variables[key] = float(input(f"Input '{key}' variable value: ")) - -res = parser.evaluate(variables) - -print(f"Evaluation result is: {res}") diff --git a/PyQt-Plotter-Dialog/parser/constants.py b/PyQt-Plotter-Dialog/parser/constants.py deleted file mode 100644 index 6b7b711..0000000 --- a/PyQt-Plotter-Dialog/parser/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -import numpy as np - -CONSTANTS = { - "e": np.e, - "pi": np.pi, -} diff --git a/PyQt-Plotter-Dialog/parser/expression.py b/PyQt-Plotter-Dialog/parser/expression.py deleted file mode 100644 index 50c579d..0000000 --- a/PyQt-Plotter-Dialog/parser/expression.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Contains classes for expression tree representation and evaluation -""" - -import abc -from collections.abc import Callable, Mapping - -from .types import FunctionType, OperatorType, ValueType - - -class Expression(abc.ABC): - """ - Abstract base class for a single parsed expression as a tree data - structure. It also defines its public function for triggering - evaluation. Each child class sets `_evaluator` property to a - function that accepts variables values and produces numpy array. - """ - - _evaluator: Callable[[Mapping[str, ValueType]], ValueType] - - def evaluate(self, variables: Mapping[str, ValueType]): - return self._evaluator(variables) - - -class ValueExpression(Expression): - """ - This expression accepts variable name, numpy array or scalar number - and evaluates to either constant or variable value, corresponding - to its name. - """ - - def __init__(self, a: str | ValueType): - self.__debug_a = a - - if isinstance(a, str): - self._evaluator = lambda vars: vars[a] - else: - self._evaluator = lambda _: a - - def __repr__(self): - return f"<{self.__debug_a}>" - - -class UnaryExpression(Expression): - """ - This expression accepts function with one argument and `Expression` - (value, unary or binary) and that function on expression, passed into it. - It is applied for named functions like `exp(a)` - """ - - def __init__(self, function: FunctionType, a: Expression): - self.__debug_f = function.__name__ - self.__debug_a = repr(a) - - self._evaluator = lambda vars: function(a.evaluate(vars)) - - def __repr__(self): - return f"<{self.__debug_f}({self.__debug_a})>" - - -class BinaryExpression(Expression): - """ - This expression is similar to `UnaryExpression`, but accepts function - with two arguments and two expressions. - It is applied for math operators like `a - b` - """ - - def __init__( - self, - function: OperatorType, - a: Expression, - b: Expression, - ): - self.__debug_f = function.__name__ - self.__debug_a = repr(a) - self.__debug_b = repr(b) - - self._evaluator = lambda vars: function(a.evaluate(vars), b.evaluate(vars)) - - def __repr__(self): - return f"<{self.__debug_a} {self.__debug_f} {self.__debug_b}>" diff --git a/PyQt-Plotter-Dialog/parser/operation.py b/PyQt-Plotter-Dialog/parser/operation.py deleted file mode 100644 index ecdeb01..0000000 --- a/PyQt-Plotter-Dialog/parser/operation.py +++ /dev/null @@ -1,113 +0,0 @@ -from dataclasses import dataclass - -import numpy as np - -from .types import FunctionType, OperatorType, ValueType - - -# Additional functions that are not defined in numpy - - -def acot(x: ValueType): - return np.arctan(1 / x) - - -def cot(x: ValueType): - return 1 / np.tan(x) - - -# Function and operation names to evaluators mapping - - -functions: dict[str, FunctionType] = { - "abs": np.abs, - "acos": np.arccos, - "acosh": np.arccosh, - "acot": acot, - "asin": np.arcsin, - "asinh": np.arcsinh, - "atan": np.arctan, - "avg": np.average, - "cos": np.cos, - "cosh": np.cosh, - "cot": cot, - "exp": np.exp, - "inf": np.inf, - "lg": np.log10, - "ln": np.log, - "log10": np.log10, - "log2": np.log2, - "max": np.max, - "min": np.min, - "prod": np.prod, - "sgn": np.sign, - "sign": np.sign, - "sin": np.sin, - "sinh": np.sinh, - "sqrt": np.sqrt, - "sum": np.sum, - "sup": np.max, - "tanh": np.tanh, - "tan": np.tan, -} - -operators: dict[str, OperatorType] = { - "+": np.add, - "-": np.subtract, - "*": np.multiply, - "/": np.divide, - "%": np.mod, - "^": np.float_power, -} - -priorities: dict[str, int] = { - "(": 0, - "+": 1, - "-": 1, - "*": 2, - "/": 2, - "%": 2, - "^": 3, - "f": 4, # function - ")": 5, -} - - -@dataclass -class Operation: - """ - Base class for math operation token (function, brace, operator). - It stores the way it is evaluated, evaluation priority and number - of arguments it supports. - """ - - evaluator: (FunctionType | OperatorType | str) - priority: int - size: int - - -class FunctionOperation(Operation): - """ - `Operator` class factory that represents function - """ - - def __init__(self, name: str): - super().__init__(functions[name], priorities["f"], 1) - - -class BraceOperation(Operation): - """ - `Operator` class factory that represents brace - """ - - def __init__(self, name: str): - super().__init__(name, priorities[name], 0) - - -class OperatorOperation(Operation): - """ - `Operator` class factory that represents binary operator - """ - - def __init__(self, name: str): - super().__init__(operators[name], priorities[name], 2) diff --git a/PyQt-Plotter-Dialog/parser/parser.py b/PyQt-Plotter-Dialog/parser/parser.py deleted file mode 100644 index c0be8d0..0000000 --- a/PyQt-Plotter-Dialog/parser/parser.py +++ /dev/null @@ -1,104 +0,0 @@ -from .expression import BinaryExpression, Expression, UnaryExpression, ValueExpression -from .operation import ( - BraceOperation, - FunctionOperation, - Operation, - OperatorOperation, - priorities, -) -from .tokenizer import Token, Tokenizer -from .types import ValueType -from .constants import CONSTANTS - - -class Parser: - """ - Class that accepts math expression in its constructor, - parses it and provides `evaluate` method to substitue - variables values to it - """ - - def __init__(self, input_expr: str): - self.input_expr = input_expr - self.variables_names: set[str] = set() - - self._tokenize() - self._parse() - - def _tokenize(self): - """ - Uses `Tokenizer` class for math expression splitting - """ - self.tokens = Tokenizer(self.input_expr) - - def _parse(self): - """ - Generates an evaluation tree from tokens by utilizing two - stacks - one for values and one for operations. Values and - operations are placed into corresponding stacks, and when - possible, assempled into tree node. - """ - self.val_stack: list[Expression] = [] - self.op_stack: list[Operation] = [] - - for t_val, t_type in self.tokens: - if t_type in (Token.Number, Token.Variable): - self.val_stack.append(ValueExpression(t_val)) - - if t_type == Token.Variable: - self.variables_names.add(t_val) - - elif t_type == Token.Function: - self.op_stack.append(FunctionOperation(t_val)) - - elif t_type == Token.LBrace: - self.op_stack.append(BraceOperation("(")) - - elif t_type == Token.RBrace: - while len(self.op_stack) > 0 and not ( - self.op_stack[-1].size == 0 and self.op_stack[-1].priority == 0 - ): # until next in stack is lbrace - self._do_one() - self.op_stack.pop() # pop lbrace - - elif t_type == Token.Operator: - t_priority = priorities[t_val] - - while ( - len(self.op_stack) > 0 and self.op_stack[-1].priority > t_priority - ): - self._do_one() - - self.op_stack.append(OperatorOperation(t_val)) - - while len(self.op_stack) > 0: - self._do_one() - - self._evaluator = self.val_stack[0].evaluate - - self.__debug_expr = repr(self.val_stack) - - def _do_one(self): - """ - Assembles one operation into `Expression` tree node that is stored - on value stack. - """ - op = self.op_stack.pop() - - if op.size == 1: - a = self.val_stack.pop() - self.val_stack.append(UnaryExpression(op.evaluator, a)) - elif op.size == 2: - b, a = self.val_stack.pop(), self.val_stack.pop() # inversed pop order - self.val_stack.append(BinaryExpression(op.evaluator, a, b)) - - def evaluate(self, variables: dict[str, ValueType]): - """ - Evaluates supplied to constructor expression with provided - variables values - """ - variables |= CONSTANTS - return self._evaluator(variables) - - def __repr__(self): - return self.__debug_expr diff --git a/PyQt-Plotter-Dialog/parser/tokenizer.py b/PyQt-Plotter-Dialog/parser/tokenizer.py deleted file mode 100644 index 4395e3b..0000000 --- a/PyQt-Plotter-Dialog/parser/tokenizer.py +++ /dev/null @@ -1,114 +0,0 @@ -from collections.abc import Generator -from dataclasses import dataclass -from typing import Optional - -from .operation import functions, operators -from .types import Token, TokenType - - -@dataclass -class Tokenizer: - """ - Implements an iterator that yields `Token`s one by one. - It's grammatics is context-sensitive, but respects only a - single previous token. - """ - - expression: str - - def __iter__(self) -> Generator[TokenType, None, None]: - accumulator = "" - prev = None - - for ch in self.expression: - if (breaker_type := Tokenizer.is_breaker(ch)) is not None: - if len(accumulator) > 0: - # ch is `(` after function name - if breaker_type == Token.LBrace and Tokenizer.is_function( - accumulator - ): - yield accumulator, Token.Function - prev = Token.Function - accumulator = "" - else: - value, token_type = Tokenizer.detect_number(accumulator) - yield value, token_type - prev = token_type - accumulator = "" - - # `(` after variable or number - if breaker_type == Token.LBrace: - yield "*", Token.Operator - prev = Token.Operator - - # Unary minus case - if ch == "-" and (prev == Token.LBrace or prev is None): - yield 0, Token.Number - prev = Token.Number - - # `(expr)(expr)` case - if breaker_type == Token.LBrace and prev == Token.RBrace: - yield "*", Token.Operator - prev = Token.Operator - - if breaker_type != Token.Space: - yield ch, breaker_type - prev = breaker_type - else: - # Variable or function name after braced expr or variable and space - if prev == Token.RBrace or prev == Token.Variable: - yield "*", Token.Operator - prev = Token.Operator - - # Floating point number - if ch in ",.": - accumulator += "." - continue - - # Variable or function name after number - if ( - not ch.isdecimal() - and (num := Tokenizer.is_number(accumulator)) is not None - ): - yield num, Token.Number - yield "*", Token.Operator - prev = Token.Operator - accumulator = "" - - accumulator += ch - if len(accumulator) > 0: - yield self.detect_number(accumulator) - - @staticmethod - def is_breaker(character) -> Optional[Token]: - if character in operators: - return Token.Operator - if character in "([{": - return Token.LBrace - if character in ")]}": - return Token.RBrace - if character == " ": - return Token.Space - - return None - - @staticmethod - def is_number(string) -> Optional[float]: - try: - return float(string) - except ValueError: - return None - - @staticmethod - def detect_number(string) -> TokenType: - if (num := Tokenizer.is_number(string)) is not None: - return num, Token.Number - else: - return string, Token.Variable - - @staticmethod - def is_function(lexeme: str) -> bool: - if lexeme in functions: - return True - - return False diff --git a/PyQt-Plotter-Dialog/parser/types.py b/PyQt-Plotter-Dialog/parser/types.py deleted file mode 100644 index f451f39..0000000 --- a/PyQt-Plotter-Dialog/parser/types.py +++ /dev/null @@ -1,23 +0,0 @@ -from collections.abc import Callable -from enum import Enum, auto - -import numpy as np - -ValueType = int | float | np.ndarray - -FunctionType = Callable[[ValueType], ValueType] - -OperatorType = Callable[[ValueType, ValueType], ValueType] - - -class Token(Enum): - Variable = auto() - Number = auto() - Function = auto() - Operator = auto() - LBrace = auto() - RBrace = auto() - Space = auto() - - -TokenType = tuple[str | float, Token] diff --git a/PyQt-Plotter-Dialog/plotter_dialog/__init__.py b/PyQt-Plotter-Dialog/plotter_dialog/__init__.py deleted file mode 100644 index 3ea9aa5..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .dialog import PlotterDialog - -__all__ = ("PlotterDialog",) diff --git a/PyQt-Plotter-Dialog/plotter_dialog/__main__.py b/PyQt-Plotter-Dialog/plotter_dialog/__main__.py deleted file mode 100644 index f88bdb6..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/__main__.py +++ /dev/null @@ -1,23 +0,0 @@ -import sys - -from PyQt5.QtWidgets import QApplication - -import numpy as np - -from . import PlotterDialog - - -def main(): - app = QApplication(sys.argv) - - variables = [chr(ord("a") + i) for i in range(10)] - - dlg = PlotterDialog( - variable_values={key: np.sort(np.random.random(10)) * 10 for key in variables}, - variable_full_names={key: key.upper() for key in variables}, - ) - dlg.show() - - sys.exit(app.exec()) - -main() diff --git a/PyQt-Plotter-Dialog/plotter_dialog/button_group.py b/PyQt-Plotter-Dialog/plotter_dialog/button_group.py deleted file mode 100644 index 0145d2c..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/button_group.py +++ /dev/null @@ -1,44 +0,0 @@ -from collections.abc import Callable - -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton - -from .flow_layout import FlowLayout - - -class ButtonGroup(QWidget): - def __init__( - self, - category: str, - full_names: dict[str, str], - buttons_action: Callable[[str], None], - parent=None, - ): - super().__init__() - self.layout = QVBoxLayout() # Создание основного лаяутв - Doplayout = FlowLayout() - label = QLabel(category) - - self.layout.addWidget(label) - for button_name in full_names: - button = QPushButton(button_name, self) - - button.setFixedWidth(80) - button.setToolTip( - full_names[button_name] - ) # Создание подскачоки при наведении - - button.clicked.connect( - lambda _, name=button_name: buttons_action( # ignore checked state with _ - name - ) - ) # Назначение кнопочке действия - - Doplayout.addWidget(button) # отрисовывание кнопок - - Doplayout.setContentsMargins(0, 0, 0, 0) - - self.layout.addLayout(Doplayout) - - self.layout.setContentsMargins(0, 0, 0, 0) - - self.setLayout(self.layout) diff --git a/PyQt-Plotter-Dialog/plotter_dialog/constants.py b/PyQt-Plotter-Dialog/plotter_dialog/constants.py deleted file mode 100644 index 60d356c..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/constants.py +++ /dev/null @@ -1,27 +0,0 @@ -FUNCTION_NAMES = { - "sin": "Синус", - "cos": "Косинус", - "tan": "Тангенс", - "cot": "Котангенс", - "abs": "Модуль", - "exp": "Экспонента (e^x)", - "sqrt": "Квадратный корень", - "ln": "Натуральный логарифм", - "lg": "Десятичный логарифм", - "log2": "Двоичный логарифм", - "sgn": "Знак", - "asin": "Арксинус", - "acos": "Арккосинус", - "atan": "Арктангенс", - "acot": "Арккотангенс", - "sinh": "Гиперболический синус", - "cosh": "Гиперболический косинус", - "tanh": "Гиперболический тангенс", - "acosh": "Гиперболический арккосинус", - "asinh": "Гиперболический арксинус", - "sum": "Сумма", - "prod": "Произведение", - "min": "Минимум", - "avg": "Среднее", - "max": "Максимум", -} diff --git a/PyQt-Plotter-Dialog/plotter_dialog/dialog.py b/PyQt-Plotter-Dialog/plotter_dialog/dialog.py deleted file mode 100644 index ac18323..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/dialog.py +++ /dev/null @@ -1,247 +0,0 @@ -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 .constants import FUNCTION_NAMES -from .graph_requester import GraphRequester -from .utils import size - -from parser import Parser - -from graph_widget import GraphWidget - - -class PlotterDialog(QDialog): - focused_line_edit = None - - def __init__( - self, - variable_values: dict[str, np.ndarray] = {}, - variable_full_names: dict[str, str] = {}, - function_full_names: dict[str, str] = FUNCTION_NAMES, - ): - 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.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) - - if size(x) != size(y): - dlg = QMessageBox( - QMessageBox.Critical, - "Ошибка", - "\n\n".join( - ( - "Выражения имеют разную размерность", - f'y: "{y}" -> {size(y)}', - f'x: "{x}" -> {size(x)}', - ) - ), - ) - dlg.exec() - - graph_requester.LineEditY.setFocus() - return - - 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() diff --git a/PyQt-Plotter-Dialog/plotter_dialog/flow_layout.py b/PyQt-Plotter-Dialog/plotter_dialog/flow_layout.py deleted file mode 100644 index e756d9d..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/flow_layout.py +++ /dev/null @@ -1,147 +0,0 @@ -############################################################################# -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -## All rights reserved. -## -## This file is part of the examples of PyQt. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor -## the names of its contributors may be used to endorse or promote -## products derived from this software without specific prior written -## permission. -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## $QT_END_LICENSE$ -## -############################################################################# - - -from PyQt5 import QtCore, QtWidgets - - -class FlowLayout(QtWidgets.QLayout): - def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1): - super(FlowLayout, self).__init__(parent) - self._hspacing = hspacing - self._vspacing = vspacing - self._items = [] - self.setContentsMargins(margin, margin, margin, margin) - - def __del__(self): - del self._items[:] - - def addItem(self, item): - self._items.append(item) - - def horizontalSpacing(self): - if self._hspacing >= 0: - return self._hspacing - else: - return self.smartSpacing(QtWidgets.QStyle.PM_LayoutHorizontalSpacing) - - def verticalSpacing(self): - if self._vspacing >= 0: - return self._vspacing - else: - return self.smartSpacing(QtWidgets.QStyle.PM_LayoutVerticalSpacing) - - def count(self): - return len(self._items) - - def itemAt(self, index): - if 0 <= index < len(self._items): - return self._items[index] - - def takeAt(self, index): - if 0 <= index < len(self._items): - return self._items.pop(index) - - def expandingDirections(self): - return QtCore.Qt.Orientations(0) - - def hasHeightForWidth(self): - return True - - def heightForWidth(self, width): - return self.doLayout(QtCore.QRect(0, 0, width, 0), True) - - def setGeometry(self, rect): - super(FlowLayout, self).setGeometry(rect) - self.doLayout(rect, False) - - def sizeHint(self): - return self.minimumSize() - - def minimumSize(self): - size = QtCore.QSize() - for item in self._items: - size = size.expandedTo(item.minimumSize()) - left, top, right, bottom = self.getContentsMargins() - size += QtCore.QSize(left + right, top + bottom) - return size - - def doLayout(self, rect, testonly): - left, top, right, bottom = self.getContentsMargins() - effective = rect.adjusted(+left, +top, -right, -bottom) - x = effective.x() - y = effective.y() - lineheight = 0 - for item in self._items: - widget = item.widget() - hspace = self.horizontalSpacing() - if hspace == -1: - hspace = widget.style().layoutSpacing( - QtWidgets.QSizePolicy.PushButton, - QtWidgets.QSizePolicy.PushButton, - QtCore.Qt.Horizontal, - ) - vspace = self.verticalSpacing() - if vspace == -1: - vspace = widget.style().layoutSpacing( - QtWidgets.QSizePolicy.PushButton, - QtWidgets.QSizePolicy.PushButton, - QtCore.Qt.Vertical, - ) - nextX = x + item.sizeHint().width() + hspace - if nextX - hspace > effective.right() and lineheight > 0: - x = effective.x() - y = y + lineheight + vspace - nextX = x + item.sizeHint().width() + hspace - lineheight = 0 - if not testonly: - item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint())) - x = nextX - lineheight = max(lineheight, item.sizeHint().height()) - return y + lineheight - rect.y() + bottom - - def smartSpacing(self, pm): - parent = self.parent() - if parent is None: - return -1 - elif parent.isWidgetType(): - return parent.style().pixelMetric(pm, None, parent) - else: - return parent.spacing() diff --git a/PyQt-Plotter-Dialog/plotter_dialog/graph_requester.py b/PyQt-Plotter-Dialog/plotter_dialog/graph_requester.py deleted file mode 100644 index fb3f0fa..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/graph_requester.py +++ /dev/null @@ -1,56 +0,0 @@ -from PyQt5.QtWidgets import ( - QVBoxLayout, - QHBoxLayout, - QLabel, - QPushButton, - QLineEdit, - QWidget, -) - -from PyQt5.QtCore import pyqtSignal - - -class FocusNotifyingLineEdit(QLineEdit): - focused_in = pyqtSignal() - - def focusInEvent(self, event): - self.focused_in.emit() - super(FocusNotifyingLineEdit, self).focusInEvent(event) - - -class GraphRequester(QWidget): - LineEditGraf: FocusNotifyingLineEdit - LineEditX: FocusNotifyingLineEdit - LineEditY: FocusNotifyingLineEdit - - def __init__(self, nomer_grafika=1): - super().__init__() - layout = QVBoxLayout(self) - layout_x = QHBoxLayout() - layout_y = QHBoxLayout() - layout_close_and_name = QHBoxLayout() - self.LineEditGraf = FocusNotifyingLineEdit(f"график {nomer_grafika}") - NameX = QLabel("X") - NameY = QLabel("Y") - Name_Close = QPushButton("x") - - self.LineEditX = FocusNotifyingLineEdit() - self.LineEditY = FocusNotifyingLineEdit() - - layout_x.addWidget(NameX) - layout_x.addWidget(self.LineEditX) - - layout_y.addWidget(NameY) - layout_y.addWidget(self.LineEditY) - - Name_Close.clicked.connect(lambda: self.setParent(None)) - - layout_close_and_name.addWidget(self.LineEditGraf) - layout_close_and_name.addWidget(Name_Close) - - layout.addLayout(layout_close_and_name) # Вложения названия и закрыть - layout.addLayout(layout_y) # Вложение - layout.addLayout(layout_x) # Вложение - - layout.setContentsMargins(0, 0, 0, 0) - layout.addStretch(1) diff --git a/PyQt-Plotter-Dialog/plotter_dialog/utils.py b/PyQt-Plotter-Dialog/plotter_dialog/utils.py deleted file mode 100644 index 07e64bc..0000000 --- a/PyQt-Plotter-Dialog/plotter_dialog/utils.py +++ /dev/null @@ -1,10 +0,0 @@ -import numpy as np - -from parser.types import ValueType - - -def size(x: ValueType) -> int: - if isinstance(x, np.ndarray): - return x.size - else: - return 1 diff --git a/PyQt-Plotter-Dialog/requirements.dev.txt b/PyQt-Plotter-Dialog/requirements.dev.txt deleted file mode 100644 index 677613b..0000000 --- a/PyQt-Plotter-Dialog/requirements.dev.txt +++ /dev/null @@ -1,6 +0,0 @@ -black==23.9.1 -mypy==1.5.1 -ruff==0.0.292 -jedi-language-server==0.41.1 -isort==5.12.0 -pytest==7.4.2 \ No newline at end of file diff --git a/PyQt-Plotter-Dialog/requirements.txt b/PyQt-Plotter-Dialog/requirements.txt deleted file mode 100644 index bb11c95..0000000 --- a/PyQt-Plotter-Dialog/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -numpy==1.26.0 -pyqt5==5.15.2 -pyqt5-qt5==5.15.2 -matplotlib==3.8.0 diff --git a/PyQt-Plotter-Dialog/tests/__init__.py b/PyQt-Plotter-Dialog/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/PyQt-Plotter-Dialog/tests/test_parser.py b/PyQt-Plotter-Dialog/tests/test_parser.py deleted file mode 100644 index 2bca931..0000000 --- a/PyQt-Plotter-Dialog/tests/test_parser.py +++ /dev/null @@ -1,14 +0,0 @@ -from parser import Parser - - -def test_Parser(): - parser = Parser("(-b + sqrt(b^2-4a c))/(2a)") - - assert parser.variables_names == {"c", "a", "b"} - - assert parser.evaluate({"a": 1, "b": -3, "c": 2}) == 1.0 - - assert all( - parser.evaluate({"a": [1, 1, 1], "b": [-5, -6, -9], "c": [6, 9, 20]}) - == [2.0, 3.0, 4.0] - )