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”:

  1. Il worker Python (LineBreakWorker) calcolava e serializzava correttamente i dati.
  2. Il widget (QCPlotlyChartWidget) riceveva il JSON corretto.
  3. Il codice JavaScript (Plotly.newPlot) veniva eseguito e riportava “successo”.
  4. 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:

  1. Generare un asse X numerico: Invece di usare line_break_df.index, l’asse X viene creato come x_axis_values = list(range(len(line_break_df))).
  2. 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 a go.Candlestick.
  3. 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.Candlestick con 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

  1. Forzare l’altezza via CSS (min-height): Scartata perché è un workaround “invasivo” che potrebbe avere effetti collaterali indesiderati sul layout.
  2. 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.
  3. Usare un tipo di traccia diverso (go.Bar): Scartata perché, sebbene funzionante, go.Candlestick rappresenta in modo semanticamente più corretto i dati Open/High/Low/Close calcolati per il grafico Line Break.