aboutsummaryrefslogtreecommitdiffstats
path: root/cogs/plot.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--cogs/plot.py188
1 files changed, 0 insertions, 188 deletions
diff --git a/cogs/plot.py b/cogs/plot.py
deleted file mode 100644
index 4fcde44..0000000
--- a/cogs/plot.py
+++ /dev/null
@@ -1,188 +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
6import asyncio
7import logging
8import math
9import os
10import re
11
12import discord
13from discord.ext import commands
14
15logger = logging.getLogger(__name__)
16
17_plt_module = None
18_plot_available = False
19
20
21def _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
38def 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
47def is_implicit(expr: str) -> bool:
48 return "=" in expr and "y" in expr and "x" in expr
49
50
51def 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
58class 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 fig, ax = plt.subplots(figsize=(10, 6))
104 ax.plot(x, y, linewidth=2, color="#00ff00")
105 ax.set_title(f"y = {func_expr}", fontsize=14)
106 ax.set_xlabel("x", fontsize=12)
107 ax.set_ylabel("y", fontsize=12)
108 ax.grid(True, alpha=0.3)
109 ax.axhline(y=0, color="white", linewidth=0.5)
110 ax.axvline(x=0, color="white", linewidth=0.5)
111 ax.set_facecolor("#1a1a1a")
112 fig.patch.set_facecolor("#1a1a1a")
113
114 plt.savefig(output_path, dpi=100, bbox_inches="tight")
115 plt.close()
116
117 def _generate_implicit_plot(self, func_expr: str, output_path: str):
118 global _plt_module
119 plt = _plt_module
120
121 lhs, rhs = parse_implicit(func_expr)
122
123 x_range = 20
124 y_range = 20
125
126 x_vals = []
127 y_vals = []
128
129 for xi in range(-x_range * 10, x_range * 10 + 1):
130 x = xi / 10.0
131 for yi in range(-y_range * 10, y_range * 10 + 1):
132 y = yi / 10.0
133 try:
134 left_val = eval(lhs, {"x": x, "y": y, "math": math, "sqrt": math.sqrt, "pi": math.pi, "e": math.e})
135 right_val = eval(rhs, {"x": x, "y": y, "math": math, "sqrt": math.sqrt, "pi": math.pi, "e": math.e})
136 diff = abs(left_val - right_val)
137 if math.isfinite(diff) and diff < 0.3:
138 x_vals.append(x)
139 y_vals.append(y)
140 except Exception:
141 pass
142
143 fig, ax = plt.subplots(figsize=(10, 10))
144 ax.scatter(x_vals, y_vals, s=1, color="#00ff00", alpha=0.5)
145 ax.set_title(f"{func_expr}", fontsize=14)
146 ax.set_xlabel("x", fontsize=12)
147 ax.set_ylabel("y", fontsize=12)
148 ax.grid(True, alpha=0.3)
149 ax.axhline(y=0, color="white", linewidth=0.5)
150 ax.axvline(x=0, color="white", linewidth=0.5)
151 ax.set_xlim(-x_range, x_range)
152 ax.set_ylim(-y_range, y_range)
153 ax.set_aspect("equal")
154 ax.set_facecolor("#1a1a1a")
155 fig.patch.set_facecolor("#1a1a1a")
156
157 plt.savefig(output_path, dpi=100, bbox_inches="tight")
158 plt.close()
159
160 @commands.slash_command(name="funkcja", description="Generuje wykres funkcji matematycznej")
161 async def plot(self, ctx, funkcja: str = discord.Option(str, "Funkcja np. x^3, sin(x), x^2 + 2*x - 1", required=True)):
162 if not _ensure_plot():
163 await ctx.respond("Generowanie wykresów nie jest dostępne (brak matplotlib)", ephemeral=True)
164 return
165
166 await ctx.defer()
167
168 from cogs.utils.temp import get_temp_dir
169 temp_path = os.path.join(get_temp_dir(), f"plot_{os.getpid()}_{asyncio.get_event_loop().time()}.png")
170
171 try:
172 loop = asyncio.get_event_loop()
173 await loop.run_in_executor(None, self._generate_plot, funkcja, temp_path)
174 await ctx.respond(file=discord.File(temp_path, filename="plot.png"))
175 logger.info(f"Generated plot for function: {funkcja}")
176 except Exception as e:
177 logger.error(f"Plot generation error: {e}")
178 await ctx.respond(f"Błąd generowania wykresu: {str(e)}", ephemeral=True)
179 finally:
180 try:
181 if os.path.exists(temp_path):
182 os.unlink(temp_path)
183 except Exception as e:
184 logger.error(f"Error cleaning up temp file: {e}")
185
186
187def setup(bot):
188 bot.add_cog(Plot(bot)) \ No newline at end of file