ADR 7: Risoluzione del Bug di Rendering a 0px per Grafici Plotly basati su go.Candlestick
Contesto
Durante lo sviluppo dei grafici avanzati (es. Line Break), abbiamo riscontrato un bug critico e persistente: il grafico appariva come un’area vuota, nonostante l’intera catena di elaborazione dati in Python e la catena di segnali Qt funzionassero perfettamente.
L’indagine, condotta tramite debugging remoto (QC DD-REMOTE-DEBUGGING-WEBENGINE.md), ha rivelato che il problema non era un errore, ma un “fallimento silente”:
- Il worker Python (
LineBreakWorker) calcolava e serializzava correttamente i dati. - Il widget (
QCPlotlyChartWidget) riceveva il JSON corretto. - Il codice JavaScript (
Plotly.newPlot) veniva eseguito e riportava “successo”. - Tuttavia, l’ispezione del DOM mostrava che il container interno del grafico (
.plotly) veniva forzato ad un’altezza di 0 pixel dopo il rendering iniziale.
Il problema si manifestava esclusivamente quando si utilizzava l’oggetto go.Candlestick per disegnare il grafico, mentre un test_plot_minimal basato su go.Scatter funzionava correttamente.
Decisione
Abbiamo identificato la causa in un’interazione anomala e poco documentata all’interno di Plotly.js. Il motore di layout per i grafici finanziari (go.Candlestick, go.ohlc) fallisce nel calcolare il “bounding box” verticale quando l’asse X non è di tipo temporale (es. datetime) o numerico monotono. Questo fallimento porta il motore a concludere che l’altezza necessaria sia zero, sovrascrivendo qualsiasi altezza specificata nel layout.
Si è deciso di adottare la soluzione più robusta, raccomandata dall’analisi degli esperti: forzare un asse X puramente numerico e monotono per tutti i grafici che utilizzano go.Candlestick in un contesto non temporale.
L’implementazione specifica nel LineBreakWorker è la seguente:
- Generare un asse X numerico: Invece di usare
line_break_df.index, l’asse X viene creato comex_axis_values = list(range(len(line_break_df))). - Passare i dati come liste native: Per prevenire problemi di serializzazione, tutti i dati del DataFrame (open, high, low, close) vengono convertiti in liste Python tramite
.tolist()prima di essere passati ago.Candlestick. - Configurare le Etichette dell’Asse: Il layout dell’asse X viene configurato per usare i valori numerici per il posizionamento (
tickvals) ma per mostrare etichette di testo personalizzate (ticktext), mantenendo la leggibilità per l’utente.
Conseguenze
Positive:
- Il bug del rendering a 0px è stato risolto in modo definitivo e affidabile.
- Il worker ora produce un output che non dipende più da comportamenti imprevedibili dell’algoritmo di auto-sizing di Plotly.
- Questa soluzione diventa uno standard di riferimento per qualsiasi futuro grafico basato su
go.Candlestickcon un asse non temporale.
Negative:
- Nessuna conseguenza negativa identificata. La soluzione è contenuta all’interno del worker e non impatta altre parti del sistema.
Alternative Considerate
- Forzare l’altezza via CSS (
min-height): Scartata perché è un workaround “invasivo” che potrebbe avere effetti collaterali indesiderati sul layout. - Forzare l’altezza via JavaScript (post-rendering): Scartata perché più complessa e fragile. Richiederebbe di modificare il codice JS per “patchare” il layout dopo ogni rendering.
- Usare un tipo di traccia diverso (
go.Bar): Scartata perché, sebbene funzionante,go.Candlestickrappresenta in modo semanticamente più corretto i dati Open/High/Low/Close calcolati per il grafico Line Break.