# VERSION: v1.8.0 - fire/ui_components/charts/qc_plotly_chart_widget.py # (pre refactoring)
# RESP: Widget Qt generico per visualizzare grafici Plotly interattivi e reattivi via JSON.
# DEPS: PySide6.QtWebEngineWidgets, ThemeManager, json.
# NOTA: Reso robusto a richieste di plot premature tramite un meccanismo di accodamento.
import json
from PySide6.QtWidgets import QWidget, QVBoxLayout
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtWebEngineCore import QWebEngineProfile, QWebEnginePage
from PySide6.QtCore import Slot
import logging
from typing import Optional
from ..theme_manager import ThemeManager
from fire.app_state import AppState
logger = logging.getLogger(__name__)
class QCPlotlyChartWidget(QWidget):
"""
Widget che renderizza una figura Plotly in modo reattivo. Riceve i dati
come stringa JSON e crea un'istanza dinamica di Plotly.js.
"""
HTML_SKELETON = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Plotly Chart</title>
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<style>
body, html {{
margin: 0; padding: 0; height: 100%; width: 100%;
overflow: hidden; background-color: {backgroundColor};
}}
#plotly-div {{ height: 100%; width: 100%; }}
</style>
</head>
<body>
<div id="plotly-div"></div>
<script>
window.renderPlotly = function(figureJson, themeColors) {{
try {{
const figure = JSON.parse(figureJson);
const plotlyTheme = {{
layout: {{
paper_bgcolor: themeColors.backgroundColor,
plot_bgcolor: themeColors.backgroundColor,
font: {{ color: themeColors.textColor }},
xaxis: {{ gridcolor: themeColors.gridColor }},
yaxis: {{ gridcolor: themeColors.gridColor }}
}}
}};
if (figure.layout) {{
Object.assign(figure.layout, plotlyTheme.layout, figure.layout);
}} else {{
figure.layout = plotlyTheme.layout;
}}
const config = {{ responsive: true, displaylogo: false }};
Plotly.newPlot('plotly-div', figure.data || [], figure.layout, config);
}} catch (e) {{
console.error("Errore durante il rendering di Plotly:", e);
}}
}};
</script>
</body>
</html>
"""
def __init__(self, app_state: AppState, parent: QWidget = None):
super().__init__(parent)
self.app_state = app_state
self.is_page_loaded = False
# --- INIZIO MODIFICA: Aggiunta coda per plot in sospeso ---
self._pending_plot_json: Optional[str] = None
# --- FINE MODIFICA ---
self._setup_theme()
self.profile = QWebEngineProfile(f"QCPlotly_{id(self)}", self)
page = QWebEnginePage(self.profile, self)
self.browser = QWebEngineView()
self.browser.setPage(page)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.browser)
formatted_html = self.HTML_SKELETON.format(backgroundColor=self.theme["backgroundColor"])
self.browser.setHtml(formatted_html)
self.browser.loadFinished.connect(self._on_load_finished)
def _setup_theme(self):
self.theme = {
"backgroundColor": ThemeManager.get_raw_color("chart_backgroundColor").name(),
"textColor": ThemeManager.get_raw_color("chart_textColor").name(),
"gridColor": ThemeManager.get_raw_color("chart_gridColor").name(),
}
def _on_load_finished(self, ok: bool):
if ok:
self.is_page_loaded = True
logger.debug("QCPlotlyChartWidget (reattivo) inizializzato e pronto.")
# --- INIZIO MODIFICA: Esecuzione del plot in sospeso ---
if self._pending_plot_json:
logger.debug("Esecuzione di un plot in sospeso...")
self.plot(self._pending_plot_json)
self._pending_plot_json = None
# --- FINE MODIFICA ---
else:
logger.error("Il caricamento della pagina HTML scheletro di Plotly è fallito.")
@Slot(str)
def plot(self, figure_json: str):
# --- INIZIO MODIFICA: Logica di accodamento ---
if not self.is_page_loaded:
logger.warning("Tentativo di plottare prima dell'inizializzazione. Metto in coda la richiesta.")
self._pending_plot_json = figure_json
return
# --- FINE MODIFICA ---
theme_json = json.dumps(self.theme)
js_code = f"window.renderPlotly({json.dumps(figure_json)}, {theme_json});"
self.browser.page().runJavaScript(js_code)
@Slot(str)
def clear_chart(self, message: str = "Nessun dato da visualizzare."):
if not self.is_page_loaded:
self._pending_plot_json = None # Annulla eventuali plot in sospeso
fig = {
"data": [],
"layout": {
"annotations": [{
"text": message, "xref": "paper", "yref": "paper",
"showarrow": False, "font": {"size": 16}
}],
"xaxis": {"visible": False},
"yaxis": {"visible": False}
}
}
self.plot(json.dumps(fig))