pyvmote 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ recursive-include Pyvmote/static *
3
+ recursive-include Pyvmote/templates *
pyvmote-0.1.5/PKG-INFO ADDED
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyvmote
3
+ Version: 0.1.5
4
+ Summary: Librería para generar gráficos y visualizar imágenes en tiempo real en remoto
5
+ Author: Juan Grima Sanchez
6
+ Author-email: Juan Grima Sanchez <juan.grimasanchez5@gmail.com>
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: uvicorn[standard]
14
+ Requires-Dist: jinja2
15
+ Requires-Dist: matplotlib
16
+ Requires-Dist: mpld3
17
+ Requires-Dist: scipy
18
+ Requires-Dist: numpy
19
+ Dynamic: author
20
+
21
+ # 📊 PyVmote
22
+
23
+ **PyVmote** es una librería de Python para la **generación y visualización remota de gráficos**, tanto estáticos como interactivos, usando un servidor FastAPI. Permite visualizar gráficas directamente desde tu navegador incluso cuando trabajas en un entorno remoto (como SSH), gracias a su sistema de forwarding de puertos y WebSocket en tiempo real.
24
+
25
+ ---
26
+
27
+ ## 🚀 Características principales
28
+
29
+ - 📈 Soporte para múltiples tipos de gráficos:
30
+ - Line plot
31
+ - Scatter plot
32
+ - Bar plot
33
+ - Histogram
34
+ - Boxplot
35
+ - Curvas de densidad (KDE)
36
+
37
+ - 🌐 Servidor web integrado con FastAPI
38
+ - ⚡ Recarga automática de gráficos mediante WebSocket
39
+ - 🌍 Visualización remota con un simple túnel SSH
40
+ - 🖱️ Soporte para gráficos **interactivos** con `mpld3`
41
+ - 📸 Exportación de gráficos a formatos `png`, `jpg`, `svg`, `pdf`, etc.
42
+ - 🧠 Historial de gráficos generado automáticamente
43
+
44
+ ---
45
+
46
+ ## 🚀 Fujo de trabajo
47
+ - import Pyvmote as p
48
+ - p.start_server(port) port exameple 3000
49
+ - p.line_plot(), p.scatter_plot(), p.box_plot(), p.bar_plot(), p.hist_plot(), p.density_plot() this will generate the graphs in the web server
50
+ - p.export_graph(title, extension, target_folder) title is also the name of the file image, extension = ["png", "jpg", "jpeg", "svg", "pdf"], target_folder = where do you want to save the photo
51
+ - p.stop_server()
@@ -0,0 +1,31 @@
1
+ # 📊 PyVmote
2
+
3
+ **PyVmote** es una librería de Python para la **generación y visualización remota de gráficos**, tanto estáticos como interactivos, usando un servidor FastAPI. Permite visualizar gráficas directamente desde tu navegador incluso cuando trabajas en un entorno remoto (como SSH), gracias a su sistema de forwarding de puertos y WebSocket en tiempo real.
4
+
5
+ ---
6
+
7
+ ## 🚀 Características principales
8
+
9
+ - 📈 Soporte para múltiples tipos de gráficos:
10
+ - Line plot
11
+ - Scatter plot
12
+ - Bar plot
13
+ - Histogram
14
+ - Boxplot
15
+ - Curvas de densidad (KDE)
16
+
17
+ - 🌐 Servidor web integrado con FastAPI
18
+ - ⚡ Recarga automática de gráficos mediante WebSocket
19
+ - 🌍 Visualización remota con un simple túnel SSH
20
+ - 🖱️ Soporte para gráficos **interactivos** con `mpld3`
21
+ - 📸 Exportación de gráficos a formatos `png`, `jpg`, `svg`, `pdf`, etc.
22
+ - 🧠 Historial de gráficos generado automáticamente
23
+
24
+ ---
25
+
26
+ ## 🚀 Fujo de trabajo
27
+ - import Pyvmote as p
28
+ - p.start_server(port) port exameple 3000
29
+ - p.line_plot(), p.scatter_plot(), p.box_plot(), p.bar_plot(), p.hist_plot(), p.density_plot() this will generate the graphs in the web server
30
+ - p.export_graph(title, extension, target_folder) title is also the name of the file image, extension = ["png", "jpg", "jpeg", "svg", "pdf"], target_folder = where do you want to save the photo
31
+ - p.stop_server()
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "pyvmote"
3
+ version = "0.1.5"
4
+ description = "Librería para generar gráficos y visualizar imágenes en tiempo real en remoto"
5
+ readme = "README.md"
6
+ requires-python = ">=3.7"
7
+ authors = [
8
+ { name="Juan Grima Sanchez", email="juan.grimasanchez5@gmail.com" }
9
+ ]
10
+ license = { text = "MIT" } # Ajusta si usas otra licencia
11
+ classifiers = [
12
+ "Programming Language :: Python :: 3",
13
+ "Operating System :: OS Independent"
14
+ ]
15
+ dependencies = [
16
+ "fastapi",
17
+ "uvicorn[standard]",
18
+ "jinja2",
19
+ "matplotlib",
20
+ "mpld3",
21
+ "scipy",
22
+ "numpy"
23
+ ]
24
+
25
+
26
+ [build-system]
27
+ requires = ["setuptools", "wheel"]
28
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,210 @@
1
+ import matplotlib.pyplot as plt
2
+ import mpld3
3
+ from mpld3 import plugins
4
+ import os
5
+ import json
6
+ from scipy.stats import gaussian_kde
7
+ import numpy as np
8
+ import re
9
+ import shutil
10
+ import warnings
11
+
12
+ class Graph:
13
+ def __init__(self):
14
+ warnings.filterwarnings("ignore", category=UserWarning)
15
+ warnings.filterwarnings("ignore", category=DeprecationWarning)
16
+
17
+ self.path = os.path.dirname(os.path.abspath(__file__))
18
+ self.n_plot = 0
19
+ self.history_file = os.path.join(self.path, "static", "graph_history.json")
20
+
21
+ if not os.path.exists(self.history_file):
22
+ with open(self.history_file, "w") as f:
23
+ json.dump([], f)
24
+
25
+ def clear_history(self):
26
+ if os.path.exists(self.history_file):
27
+ os.remove(self.history_file)
28
+ with open(self.history_file, "w") as f:
29
+ json.dump([], f)
30
+
31
+ def save_graph_to_history(self, graph_name, graph_type, title):
32
+ with open(self.history_file, "r") as f:
33
+ history = json.load(f)
34
+
35
+ # Verifica si ya existe un gráfico con ese título
36
+ for graph in history:
37
+ if graph["title"] == title:
38
+ raise ValueError(f"Ya existe un gráfico con el título '{title}'. Usa uno diferente.")
39
+
40
+ history.append({"name": graph_name, "type": graph_type, "title": title})
41
+
42
+ with open(self.history_file, "w") as f:
43
+ json.dump(history, f)
44
+
45
+ def _sanitize_title(self, title):
46
+ return re.sub(r'[^a-zA-Z0-9_-]', '_', title.replace(' ', '_'))
47
+
48
+ def _prepare_paths(self, title):
49
+ images_dir = os.path.join(self.path, "static", "images")
50
+ html_dir = os.path.join(self.path, "static", "html")
51
+ os.makedirs(images_dir, exist_ok=True)
52
+ os.makedirs(html_dir, exist_ok=True)
53
+
54
+ safe_title = self._sanitize_title(title)
55
+ image_name = f"{safe_title}.png"
56
+ html_name = f"{safe_title}.html"
57
+
58
+ return images_dir, html_dir, image_name, html_name
59
+
60
+ def _finalize_plot(self, fig, image_path, html_path, labels, scatter, plot_type, interactive, title):
61
+ if interactive:
62
+ if labels and plot_type in ["line", "scatter", "bar", "density"]:
63
+ if plot_type == "bar":
64
+ for rect, label in zip(scatter, labels):
65
+ plugins.connect(fig, plugins.LineLabelTooltip(rect, label))
66
+ else:
67
+ plugins.connect(fig, plugins.PointLabelTooltip(scatter[0], labels=labels))
68
+
69
+ mpld3.save_html(fig, html_path)
70
+ self.save_graph_to_history(os.path.basename(html_path), "html", title)
71
+ plt.close()
72
+ return os.path.basename(html_path)
73
+ else:
74
+ plt.savefig(image_path, dpi=300, bbox_inches='tight')
75
+ self.save_graph_to_history(os.path.basename(image_path), "image", title)
76
+ plt.close()
77
+ return os.path.basename(image_path)
78
+
79
+ def save_as_format(self, fig, title, extension="png"):
80
+ valid_exts = ["png", "jpg", "jpeg", "svg", "pdf"]
81
+ if extension not in valid_exts:
82
+ raise ValueError(f"Formato no soportado: {extension}")
83
+
84
+ output_dir = os.path.join(self.path, "static", "images")
85
+ os.makedirs(output_dir, exist_ok=True)
86
+ safe_title = self._sanitize_title(title)
87
+ output_path = os.path.join(output_dir, f"{safe_title}.{extension}")
88
+ fig.savefig(output_path, dpi=300, bbox_inches='tight')
89
+ return output_path
90
+
91
+ def line_plot(self, x, y, xname="X", yname="Y", title="Line Graph", interactive=True, color='blue', linewidth=2, xlim=None, ylim=None):
92
+ fig, ax = plt.subplots(figsize=(12, 7))
93
+ scatter = ax.plot(x, y, marker='o', linestyle='-', color=color, linewidth=linewidth)
94
+ labels = [f"({xi}, {yi})" for xi, yi in zip(x, y)]
95
+ ax.set_xlabel(xname)
96
+ ax.set_ylabel(yname)
97
+ ax.set_title(title)
98
+ if xlim: ax.set_xlim(xlim)
99
+ if ylim: ax.set_ylim(ylim)
100
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
101
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), labels, scatter, "line", interactive, title)
102
+
103
+
104
+ def scatter_plot(self, x, y, xname="X", yname="Y", title="Scatter Plot", interactive=True, color='blue', xlim=None, ylim=None):
105
+ fig, ax = plt.subplots(figsize=(12, 7))
106
+ scatter = ax.scatter(x, y, color=color)
107
+ labels = [f"({xi}, {yi})" for xi, yi in zip(x, y)]
108
+ ax.set_xlabel(xname)
109
+ ax.set_ylabel(yname)
110
+ ax.set_title(title)
111
+ if xlim: ax.set_xlim(xlim)
112
+ if ylim: ax.set_ylim(ylim)
113
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
114
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), labels, [scatter], "scatter", interactive, title)
115
+
116
+ def bar_plot(self, x, y, xname="X", yname="Y", title="Bar Plot", interactive=True, color='blue', xlim=None, ylim=None):
117
+ fig, ax = plt.subplots(figsize=(12, 7))
118
+ scatter = ax.bar(x, y, color=color)
119
+ labels = [f"({xi}, {yi})" for xi, yi in zip(x, y)]
120
+ ax.set_xlabel(xname)
121
+ ax.set_ylabel(yname)
122
+ ax.set_title(title)
123
+ if xlim: ax.set_xlim(xlim)
124
+ if ylim: ax.set_ylim(ylim)
125
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
126
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), labels, scatter, "bar", interactive, title)
127
+
128
+ def hist_plot(self, x, xname="Value", yname="Frequency", title="Histogram", bins=20, interactive=True, color='blue', xlim=None, ylim=None):
129
+ fig, ax = plt.subplots(figsize=(12, 7))
130
+ scatter = ax.hist(x, bins=bins, edgecolor='black', color=color)
131
+ ax.set_xlabel(xname)
132
+ ax.set_ylabel(yname)
133
+ ax.set_title(title)
134
+ if xlim: ax.set_xlim(xlim)
135
+ if ylim: ax.set_ylim(ylim)
136
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
137
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), [], [], "hist", interactive, title)
138
+
139
+ def box_plot(self, x, xname="", yname="Value", title="Box Plot", interactive=True):
140
+ fig, ax = plt.subplots(figsize=(12, 7))
141
+ scatter = ax.boxplot(x)
142
+ ax.set_ylabel(yname)
143
+ ax.set_xlabel(xname)
144
+ ax.set_title(title)
145
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
146
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), [], [], "box", interactive, title)
147
+
148
+ def density_plot(self, x, xname="X", yname="Density", title="Density Plot", interactive=True, color='blue', xlim=None, ylim=None):
149
+ fig, ax = plt.subplots(figsize=(12, 7))
150
+ kde = gaussian_kde(x)
151
+ x_vals = np.linspace(min(x), max(x), 200)
152
+ y_vals = kde(x_vals)
153
+ scatter = ax.plot(x_vals, y_vals, color=color)
154
+ labels = [f"({xi:.2f}, {yi:.2f})" for xi, yi in zip(x_vals, y_vals)]
155
+ ax.set_xlabel(xname)
156
+ ax.set_ylabel(yname)
157
+ ax.set_title(title)
158
+ if xlim: ax.set_xlim(xlim)
159
+ if ylim: ax.set_ylim(ylim)
160
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
161
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), labels, scatter, "density", interactive, title)
162
+
163
+ def pie_plot(self, sizes, labels=None, title="Pie Chart", interactive=True, colors=None):
164
+ fig, ax = plt.subplots(figsize=(8, 8))
165
+ ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors)
166
+ ax.set_title(title)
167
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
168
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), [], [], "pie", interactive, title)
169
+
170
+ def cluster_plot(self, data, labels, title="Cluster Plot", interactive=True, cmap='viridis', xlim=None, ylim=None):
171
+ fig, ax = plt.subplots(figsize=(12, 7))
172
+ scatter = ax.scatter(data[:, 0], data[:, 1], c=labels, cmap=cmap)
173
+ ax.set_title(title)
174
+ ax.set_xlabel("X")
175
+ ax.set_ylabel("Y")
176
+ if xlim: ax.set_xlim(xlim)
177
+ if ylim: ax.set_ylim(ylim)
178
+ images_dir, html_dir, image_name, html_name = self._prepare_paths(title)
179
+ return self._finalize_plot(fig, os.path.join(images_dir, image_name), os.path.join(html_dir, html_name), [], [scatter], "cluster", interactive, title)
180
+
181
+ # 4. Personalización en otros gráficos
182
+
183
+
184
+ def save_as_format(self, title, extension="png", target_folder="exports"):
185
+
186
+ valid_exts = ["png", "jpg", "jpeg", "svg", "pdf"]
187
+ if extension not in valid_exts:
188
+ raise ValueError(f"Formato no soportado: {extension}")
189
+
190
+ safe_title = self._sanitize_title(title)
191
+ original_path = os.path.join(self.path, "static", "images", f"{safe_title}.png")
192
+
193
+ if not os.path.exists(original_path):
194
+ raise FileNotFoundError(f"No se encontró el gráfico original: {original_path}")
195
+
196
+ # Crear carpeta destino si no existe
197
+ os.makedirs(target_folder, exist_ok=True)
198
+ output_path = os.path.join(target_folder, f"{safe_title}.{extension}")
199
+
200
+ # Si el formato es el mismo, simplemente copiar
201
+ if extension == "png":
202
+ shutil.copyfile(original_path, output_path)
203
+ else:
204
+ # Leer imagen y volver a guardarla en nuevo formato
205
+ from PIL import Image
206
+ with Image.open(original_path) as img:
207
+ format_map = {"jpg": "JPEG", "jpeg": "JPEG", "png": "PNG", "pdf": "PDF", "svg": "SVG"}
208
+ img.convert("RGB").save(output_path, format_map[extension.lower()])
209
+
210
+ return output_path
@@ -0,0 +1,165 @@
1
+ from fastapi import FastAPI, WebSocketDisconnect, WebSocket, Request
2
+ from fastapi.responses import JSONResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.templating import Jinja2Templates
5
+ import uvicorn
6
+ import os
7
+ import asyncio
8
+ import threading
9
+ import shutil
10
+ import json
11
+
12
+ class Server:
13
+ def __init__(self):
14
+ self.port = ""
15
+ self.app = FastAPI()
16
+ self.ws_connection = None
17
+ self.last_timestamp = 0
18
+ self.loop = None
19
+ self.start = False
20
+ self.init_server()
21
+
22
+ def init_server(self):
23
+ self.base_path = os.path.dirname(os.path.abspath(__file__))
24
+ self.image_folder = os.path.join(self.base_path, "static", "images")
25
+ self.html_folder = os.path.join(self.base_path, "static", "html")
26
+ self.templates_path = os.path.join(self.base_path, "templates")
27
+
28
+ self.app.mount("/static", StaticFiles(directory=os.path.join(self.base_path, "static")), name="static")
29
+
30
+ self.templates = Jinja2Templates(directory=self.templates_path)
31
+
32
+ self.app.get("/")(self.index)
33
+ self.app.add_api_route("/preview", self.preview_page)
34
+ self.app.get("/latest")(self.latest_image)
35
+ self.app.add_api_route("/rename", self.rename_graph, methods=["POST"])
36
+
37
+ self.app.on_event("startup")(self.startup_event)
38
+ self.app.websocket("/ws")(self.websocket_endpoint)
39
+
40
+ async def startup_event(self):
41
+ self.loop = asyncio.get_running_loop()
42
+
43
+ async def websocket_endpoint(self, websocket: WebSocket):
44
+ await websocket.accept()
45
+ self.ws_connection = websocket
46
+ try:
47
+ while True:
48
+ await websocket.receive_text()
49
+ except WebSocketDisconnect:
50
+ self.ws_connection = None
51
+
52
+ def get_image_files(self):
53
+ valid_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.bmp')
54
+ if not os.path.exists(self.image_folder):
55
+ return []
56
+ return [f for f in os.listdir(self.image_folder) if f.lower().endswith(valid_extensions)]
57
+
58
+ def get_html_files(self):
59
+ if not os.path.exists(self.html_folder):
60
+ return []
61
+ return [f for f in os.listdir(self.html_folder) if f.endswith(".html")]
62
+
63
+ def get_latest_graphs(self):
64
+ images = self.get_image_files()
65
+ htmls = self.get_html_files()
66
+ return images, htmls
67
+
68
+ async def index(self, request: Request):
69
+ latest_img, latest_html = self.get_latest_graphs()
70
+ return self.templates.TemplateResponse("index.html", {"request": request, "latest_img": latest_img[0] if latest_img else None, "latest_html": latest_html[0] if latest_html else None})
71
+
72
+ async def preview_page(self,request: Request):
73
+ graphs = self.get_ordered_graphs()
74
+ return self.templates.TemplateResponse("preview.html", {"request": request, "graphs": graphs})
75
+
76
+
77
+ def show_port(self):
78
+ print(f"Servidor corriendo en http://localhost:{self.port}")
79
+
80
+ def start_server(self, port):
81
+ self.port = port
82
+ self.start = True
83
+ config = uvicorn.Config(self.app, host="0.0.0.0", port=self.port, log_level="warning")
84
+ self.server = uvicorn.Server(config)
85
+ self.server_thread = threading.Thread(target=self.server.run, daemon=True)
86
+ self.server_thread.start()
87
+ self.show_port()
88
+
89
+ def get_ordered_graphs(self):
90
+ """Lee el historial de gráficos en el orden en que se generaron."""
91
+ history_file = os.path.join(self.base_path, "static", "graph_history.json")
92
+
93
+ if not os.path.exists(history_file):
94
+ return []
95
+
96
+ with open(history_file, "r") as f:
97
+ return json.load(f)
98
+
99
+ async def latest_image(self):
100
+ """
101
+ Devuelve la lista de gráficos en formato JSON, manteniendo el orden de creación.
102
+ """
103
+ graphs = self.get_ordered_graphs()
104
+ return JSONResponse(content={"graphs": graphs})
105
+
106
+ def notify_update(self):
107
+ if self.ws_connection is not None and self.loop is not None:
108
+ asyncio.run_coroutine_threadsafe(self.msg_notify_update(), self.loop)
109
+
110
+ async def msg_notify_update(self):
111
+ try:
112
+ await self.ws_connection.send_text("update")
113
+ except Exception:
114
+ self.ws_connection = None
115
+
116
+ def stop_server(self):
117
+ if self.server is not None:
118
+ self.start = False
119
+ self.server.should_exit = True
120
+ self.clear_graphs()
121
+ if hasattr(self, 'server_thread') and self.server_thread is not None:
122
+ self.server_thread.join()
123
+ print("Servidor detenido.")
124
+
125
+ def clear_graphs(self):
126
+ """
127
+ Borra todo el contenido de la carpeta de imágenes y de gráficos interactivos.
128
+ """
129
+ for folder in [self.image_folder, self.html_folder]:
130
+ if os.path.exists(folder):
131
+ for filename in os.listdir(folder):
132
+ file_path = os.path.join(folder, filename)
133
+ try:
134
+ if os.path.isfile(file_path) or os.path.islink(file_path):
135
+ os.unlink(file_path)
136
+ elif os.path.isdir(file_path):
137
+ shutil.rmtree(file_path)
138
+ except Exception as e:
139
+ print(f"No se pudo borrar {file_path}. Motivo: {e}")
140
+
141
+ async def rename_graph(self, request: Request):
142
+ data = await request.json()
143
+ old_title = data.get("old_title")
144
+ new_title = data.get("new_title")
145
+
146
+ history_file = os.path.join(self.base_path, "static", "graph_history.json")
147
+ if not os.path.exists(history_file):
148
+ return JSONResponse(content={"error": "No hay historial"}, status_code=404)
149
+
150
+ with open(history_file, "r") as f:
151
+ history = json.load(f)
152
+
153
+ updated = False
154
+ for graph in history:
155
+ if graph["title"] == old_title:
156
+ graph["title"] = new_title
157
+ updated = True
158
+ break
159
+
160
+ if updated:
161
+ with open(history_file, "w") as f:
162
+ json.dump(history, f)
163
+ return JSONResponse(content={"message": "Título actualizado"})
164
+ else:
165
+ return JSONResponse(content={"error": "Gráfico no encontrado"}, status_code=404)
@@ -0,0 +1,8 @@
1
+ from .pyvmote import Pyvmote
2
+
3
+ __version__ = "0.1.5"
4
+
5
+ # Creamos una instancia de Pyvmote para que al importar el paquete se obtenga directamente el objeto
6
+ _instance = Pyvmote()
7
+ import sys
8
+ sys.modules[__name__] = _instance
@@ -0,0 +1,70 @@
1
+ from .Graph_generator import Graph
2
+ from .Service import Server
3
+
4
+ class Pyvmote:
5
+ def __init__(self):
6
+ self.gr = Graph()
7
+ self.sv = Server()
8
+
9
+ def start_server(self, puerto):
10
+ self.sv.start_server(puerto)
11
+
12
+ def stop_server(self):
13
+ self.sv.stop_server()
14
+ self.gr.clear_history()
15
+
16
+ def line_plot(self, x, y, xname="X", yname="Y", title="Line Graph", interactive=True, color='blue', linewidth=2, xlim=None, ylim=None):
17
+ plot_file = self.gr.line_plot(x, y, xname, yname, title, interactive, color, linewidth, xlim, ylim)
18
+ if self.sv.start:
19
+ self.sv.notify_update()
20
+ return plot_file
21
+
22
+ def scatter_plot(self, x, y, xname="X", yname="Y", title="Scatter Plot", interactive=True, color='blue', xlim=None, ylim=None):
23
+ plot_file = self.gr.scatter_plot(x, y, xname, yname, title, interactive, color, xlim, ylim)
24
+ if self.sv.start:
25
+ self.sv.notify_update()
26
+ return plot_file
27
+
28
+ def bar_plot(self, x, y, xname="X", yname="Y", title="Bar Plot", interactive=True, color='blue', xlim=None, ylim=None):
29
+ plot_file = self.gr.bar_plot(x, y, xname, yname, title, interactive, color, xlim, ylim)
30
+ if self.sv.start:
31
+ self.sv.notify_update()
32
+ return plot_file
33
+
34
+ def hist_plot(self, x, xname="Value", yname="Frequency", title="Histogram", bins=20, interactive=True, color='blue', xlim=None, ylim=None):
35
+ plot_file = self.gr.hist_plot(x, xname, yname, title, bins, interactive, color, xlim, ylim)
36
+ if self.sv.start:
37
+ self.sv.notify_update()
38
+ return plot_file
39
+
40
+ def box_plot(self, x, xname="", yname="Value", title="Box Plot", interactive=True):
41
+ plot_file = self.gr.box_plot(x, xname, yname, title, interactive)
42
+ if self.sv.start:
43
+ self.sv.notify_update()
44
+ return plot_file
45
+
46
+ def density_plot(self, x, xname="X", yname="Density", title="Density Plot", interactive=True, color='blue', xlim=None, ylim=None):
47
+ plot_file = self.gr.density_plot(x, xname, yname, title, interactive, color, xlim, ylim)
48
+ if self.sv.start:
49
+ self.sv.notify_update()
50
+ return plot_file
51
+
52
+ def pie_plot(self, sizes, labels=None, title="Pie Chart", interactive=True, colors=None):
53
+ plot_file = self.gr.pie_plot(sizes, labels, title, interactive, colors)
54
+ if self.sv.start:
55
+ self.sv.notify_update()
56
+ return plot_file
57
+
58
+ def cluster_plot(self, data, labels, title="Cluster Plot", interactive=True, cmap='viridis', xlim=None, ylim=None):
59
+ plot_file = self.gr.cluster_plot(data, labels, title, interactive, cmap, xlim, ylim)
60
+ if self.sv.start:
61
+ self.sv.notify_update()
62
+ return plot_file
63
+
64
+ def export_graph(self, title, extension="jpg", target_folder="exports"):
65
+ ruta = self.gr.save_as_format(title, extension, target_folder)
66
+ print(ruta)
67
+
68
+
69
+
70
+
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyvmote
3
+ Version: 0.1.5
4
+ Summary: Librería para generar gráficos y visualizar imágenes en tiempo real en remoto
5
+ Author: Juan Grima Sanchez
6
+ Author-email: Juan Grima Sanchez <juan.grimasanchez5@gmail.com>
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: fastapi
13
+ Requires-Dist: uvicorn[standard]
14
+ Requires-Dist: jinja2
15
+ Requires-Dist: matplotlib
16
+ Requires-Dist: mpld3
17
+ Requires-Dist: scipy
18
+ Requires-Dist: numpy
19
+ Dynamic: author
20
+
21
+ # 📊 PyVmote
22
+
23
+ **PyVmote** es una librería de Python para la **generación y visualización remota de gráficos**, tanto estáticos como interactivos, usando un servidor FastAPI. Permite visualizar gráficas directamente desde tu navegador incluso cuando trabajas en un entorno remoto (como SSH), gracias a su sistema de forwarding de puertos y WebSocket en tiempo real.
24
+
25
+ ---
26
+
27
+ ## 🚀 Características principales
28
+
29
+ - 📈 Soporte para múltiples tipos de gráficos:
30
+ - Line plot
31
+ - Scatter plot
32
+ - Bar plot
33
+ - Histogram
34
+ - Boxplot
35
+ - Curvas de densidad (KDE)
36
+
37
+ - 🌐 Servidor web integrado con FastAPI
38
+ - ⚡ Recarga automática de gráficos mediante WebSocket
39
+ - 🌍 Visualización remota con un simple túnel SSH
40
+ - 🖱️ Soporte para gráficos **interactivos** con `mpld3`
41
+ - 📸 Exportación de gráficos a formatos `png`, `jpg`, `svg`, `pdf`, etc.
42
+ - 🧠 Historial de gráficos generado automáticamente
43
+
44
+ ---
45
+
46
+ ## 🚀 Fujo de trabajo
47
+ - import Pyvmote as p
48
+ - p.start_server(port) port exameple 3000
49
+ - p.line_plot(), p.scatter_plot(), p.box_plot(), p.bar_plot(), p.hist_plot(), p.density_plot() this will generate the graphs in the web server
50
+ - p.export_graph(title, extension, target_folder) title is also the name of the file image, extension = ["png", "jpg", "jpeg", "svg", "pdf"], target_folder = where do you want to save the photo
51
+ - p.stop_server()
@@ -0,0 +1,13 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ pyvmote/Graph_generator.py
6
+ pyvmote/Service.py
7
+ pyvmote/__init__.py
8
+ pyvmote/pyvmote.py
9
+ pyvmote.egg-info/PKG-INFO
10
+ pyvmote.egg-info/SOURCES.txt
11
+ pyvmote.egg-info/dependency_links.txt
12
+ pyvmote.egg-info/requires.txt
13
+ pyvmote.egg-info/top_level.txt
@@ -0,0 +1,7 @@
1
+ fastapi
2
+ uvicorn[standard]
3
+ jinja2
4
+ matplotlib
5
+ mpld3
6
+ scipy
7
+ numpy
@@ -0,0 +1 @@
1
+ pyvmote
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
pyvmote-0.1.5/setup.py ADDED
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="pyvmote",
5
+ version="0.1.5",
6
+ packages=find_packages(),
7
+ include_package_data=True,
8
+ install_requires=[
9
+ "fastapi",
10
+ "uvicorn[standard]",
11
+ "jinja2",
12
+ "matplotlib",
13
+ "mpld3",
14
+ "scipy",
15
+ "numpy"
16
+ ],
17
+ author="Juan Grima Sanchez",
18
+ author_email="juan.grimasanchez5@gmail.com",
19
+ description="Librería para generar gráficos y visualizar imágenes en tiempo real en remoto",
20
+ classifiers=[
21
+ "Programming Language :: Python :: 3",
22
+ "Operating System :: OS Independent",
23
+ ],
24
+ )