diff options
Diffstat (limited to 'cogs/plot.py')
| -rw-r--r-- | cogs/plot.py | 195 |
1 files changed, 0 insertions, 195 deletions
diff --git a/cogs/plot.py b/cogs/plot.py deleted file mode 100644 index ee19de9..0000000 --- a/cogs/plot.py +++ /dev/null | |||
| @@ -1,195 +0,0 @@ | |||
| 1 | # This file is part of Senator Sperma | ||
| 2 | # Licensed under the GPL-3.0 License | ||
| 3 | # Copyright (C) 2026 Salmonik @ salmonik@tuta.io | ||
| 4 | # https://codeberg.org/salmonik/senator-sperma | ||
| 5 | |||
| 6 | import asyncio | ||
| 7 | import logging | ||
| 8 | import math | ||
| 9 | import os | ||
| 10 | import re | ||
| 11 | |||
| 12 | import discord | ||
| 13 | from discord.ext import commands | ||
| 14 | |||
| 15 | logger = logging.getLogger(__name__) | ||
| 16 | |||
| 17 | _plt_module = None | ||
| 18 | _plot_available = False | ||
| 19 | |||
| 20 | |||
| 21 | def _ensure_plot(): | ||
| 22 | global _plt_module, _plot_available | ||
| 23 | |||
| 24 | if _plt_module is not None: | ||
| 25 | return _plot_available | ||
| 26 | |||
| 27 | try: | ||
| 28 | import matplotlib | ||
| 29 | matplotlib.use("Agg") | ||
| 30 | import matplotlib.pyplot as _plt_module | ||
| 31 | _plot_available = True | ||
| 32 | except ImportError: | ||
| 33 | _plot_available = False | ||
| 34 | logger.warning("matplotlib not available, plot command will be disabled") | ||
| 35 | return _plot_available | ||
| 36 | |||
| 37 | |||
| 38 | def parse_expression(expr: str) -> str: | ||
| 39 | expr = expr.replace("^", "**") | ||
| 40 | expr = re.sub(r"(\d)([x])", r"\1*\2", expr) | ||
| 41 | expr = re.sub(r"([x])(\d)", r"\1*\2", expr) | ||
| 42 | expr = re.sub(r"(\d)([y])", r"\1*\2", expr) | ||
| 43 | expr = re.sub(r"([y])(\d)", r"\1*\2", expr) | ||
| 44 | return expr | ||
| 45 | |||
| 46 | |||
| 47 | def is_implicit(expr: str) -> bool: | ||
| 48 | return "=" in expr and "y" in expr and "x" in expr | ||
| 49 | |||
| 50 | |||
| 51 | def parse_implicit(expr: str) -> tuple[str, str]: | ||
| 52 | lhs, rhs = expr.split("=", 1) | ||
| 53 | lhs = parse_expression(lhs.strip()) | ||
| 54 | rhs = parse_expression(rhs.strip()) | ||
| 55 | return lhs, rhs | ||
| 56 | |||
| 57 | |||
| 58 | class Plot(commands.Cog): | ||
| 59 | def __init__(self, bot): | ||
| 60 | self.bot = bot | ||
| 61 | |||
| 62 | def _generate_plot(self, func_expr: str, output_path: str): | ||
| 63 | global _plt_module | ||
| 64 | |||
| 65 | if not _ensure_plot(): | ||
| 66 | raise RuntimeError("matplotlib not available") | ||
| 67 | |||
| 68 | plt = _plt_module | ||
| 69 | |||
| 70 | if is_implicit(func_expr): | ||
| 71 | self._generate_implicit_plot(func_expr, output_path) | ||
| 72 | return | ||
| 73 | |||
| 74 | parsed_expr = parse_expression(func_expr) | ||
| 75 | |||
| 76 | x = [i * 0.1 for i in range(-100, 101)] | ||
| 77 | y = [] | ||
| 78 | |||
| 79 | for xi in x: | ||
| 80 | try: | ||
| 81 | result = eval( | ||
| 82 | parsed_expr, | ||
| 83 | { | ||
| 84 | "x": xi, | ||
| 85 | "math": math, | ||
| 86 | "sin": math.sin, | ||
| 87 | "cos": math.cos, | ||
| 88 | "tan": math.tan, | ||
| 89 | "sqrt": math.sqrt, | ||
| 90 | "log": math.log, | ||
| 91 | "exp": math.exp, | ||
| 92 | "pi": math.pi, | ||
| 93 | "e": math.e, | ||
| 94 | }, | ||
| 95 | ) | ||
| 96 | if math.isfinite(result): | ||
| 97 | y.append(result) | ||
| 98 | else: | ||
| 99 | y.append(float("nan")) | ||
| 100 | except Exception: | ||
| 101 | y.append(float("nan")) | ||
| 102 | |||
| 103 | valid_y = [yi for yi in y if not math.isnan(yi)] | ||
| 104 | if not valid_y: | ||
| 105 | raise ValueError("Brak danych do wykreślenia dla podanej funkcji") | ||
| 106 | |||
| 107 | fig, ax = plt.subplots(figsize=(10, 6)) | ||
| 108 | ax.plot(x, y, linewidth=2, color="#00ff00") | ||
| 109 | ax.set_title(f"y = {func_expr}", fontsize=14) | ||
| 110 | ax.set_xlabel("x", fontsize=12) | ||
| 111 | ax.set_ylabel("y", fontsize=12) | ||
| 112 | ax.grid(True, alpha=0.3) | ||
| 113 | ax.axhline(y=0, color="white", linewidth=0.5) | ||
| 114 | ax.axvline(x=0, color="white", linewidth=0.5) | ||
| 115 | ax.set_facecolor("#1a1a1a") | ||
| 116 | fig.patch.set_facecolor("#1a1a1a") | ||
| 117 | |||
| 118 | plt.savefig(output_path, dpi=100, bbox_inches="tight") | ||
| 119 | plt.close() | ||
| 120 | |||
| 121 | def _generate_implicit_plot(self, func_expr: str, output_path: str): | ||
| 122 | global _plt_module | ||
| 123 | plt = _plt_module | ||
| 124 | |||
| 125 | lhs, rhs = parse_implicit(func_expr) | ||
| 126 | |||
| 127 | x_range = 20 | ||
| 128 | y_range = 20 | ||
| 129 | |||
| 130 | x_vals = [] | ||
| 131 | y_vals = [] | ||
| 132 | |||
| 133 | for xi in range(-x_range * 10, x_range * 10 + 1): | ||
| 134 | x = xi / 10.0 | ||
| 135 | for yi in range(-y_range * 10, y_range * 10 + 1): | ||
| 136 | y = yi / 10.0 | ||
| 137 | try: | ||
| 138 | left_val = eval(lhs, {"x": x, "y": y, "math": math, "sqrt": math.sqrt, "pi": math.pi, "e": math.e}) | ||
| 139 | right_val = eval(rhs, {"x": x, "y": y, "math": math, "sqrt": math.sqrt, "pi": math.pi, "e": math.e}) | ||
| 140 | diff = abs(left_val - right_val) | ||
| 141 | if math.isfinite(diff) and diff < 0.3: | ||
| 142 | x_vals.append(x) | ||
| 143 | y_vals.append(y) | ||
| 144 | except Exception: | ||
| 145 | pass | ||
| 146 | |||
| 147 | if not x_vals or not y_vals: | ||
| 148 | raise ValueError("Brak danych do wykreślenia dla podanej funkcji") | ||
| 149 | |||
| 150 | fig, ax = plt.subplots(figsize=(10, 10)) | ||
| 151 | ax.scatter(x_vals, y_vals, s=1, color="#00ff00", alpha=0.5) | ||
| 152 | ax.set_title(f"{func_expr}", fontsize=14) | ||
| 153 | ax.set_xlabel("x", fontsize=12) | ||
| 154 | ax.set_ylabel("y", fontsize=12) | ||
| 155 | ax.grid(True, alpha=0.3) | ||
| 156 | ax.axhline(y=0, color="white", linewidth=0.5) | ||
| 157 | ax.axvline(x=0, color="white", linewidth=0.5) | ||
| 158 | ax.set_xlim(-x_range, x_range) | ||
| 159 | ax.set_ylim(-y_range, y_range) | ||
| 160 | ax.set_aspect("equal") | ||
| 161 | ax.set_facecolor("#1a1a1a") | ||
| 162 | fig.patch.set_facecolor("#1a1a1a") | ||
| 163 | |||
| 164 | plt.savefig(output_path, dpi=100, bbox_inches="tight") | ||
| 165 | plt.close() | ||
| 166 | |||
| 167 | @commands.slash_command(name="funkcja", description="Generuje wykres funkcji matematycznej") | ||
| 168 | async def plot(self, ctx, funkcja: str = discord.Option(str, "Funkcja np. x^3, sin(x), x^2 + 2*x - 1", required=True)): | ||
| 169 | if not _ensure_plot(): | ||
| 170 | await ctx.respond("Generowanie wykresów nie jest dostępne (brak matplotlib)", ephemeral=True) | ||
| 171 | return | ||
| 172 | |||
| 173 | await ctx.defer() | ||
| 174 | |||
| 175 | from cogs.utils.temp import get_temp_dir | ||
| 176 | temp_path = os.path.join(get_temp_dir(), f"plot_{os.getpid()}_{asyncio.get_event_loop().time()}.png") | ||
| 177 | |||
| 178 | try: | ||
| 179 | loop = asyncio.get_event_loop() | ||
| 180 | await loop.run_in_executor(None, self._generate_plot, funkcja, temp_path) | ||
| 181 | await ctx.respond(file=discord.File(temp_path, filename="plot.png")) | ||
| 182 | logger.info(f"Generated plot for function: {funkcja}") | ||
| 183 | except Exception as e: | ||
| 184 | logger.error(f"Plot generation error: {e}") | ||
| 185 | await ctx.respond(f"Błąd generowania wykresu: {str(e)}", ephemeral=True) | ||
| 186 | finally: | ||
| 187 | try: | ||
| 188 | if os.path.exists(temp_path): | ||
| 189 | os.unlink(temp_path) | ||
| 190 | except Exception as e: | ||
| 191 | logger.error(f"Error cleaning up temp file: {e}") | ||
| 192 | |||
| 193 | |||
| 194 | def setup(bot): | ||
| 195 | bot.add_cog(Plot(bot)) | ||
