Creating charts
Charts in QRY are produced by Python code that uses matplotlib (static, fast, native executor) or plotly (interactive, K8s executor). Both work; the conventions are different. Following them gets you crisp, single-copy, clickable charts. Ignoring them produces duplicates, surprise saves to disk, or charts that don't render.
matplotlib
The rule: plt.show() and nothing else
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
plt.plot(df['month'], df['revenue'])
plt.title('Monthly revenue')
plt.xlabel('Month')
plt.ylabel('EUR')
plt.show() # captured inline by QRY
The capture happens because of plt.show(). QRY's display hook intercepts the figure and renders it in the conversation / notebook output.
The anti-pattern: plt.savefig
plt.savefig('chart.png') # ❌ DON'T
plt.show()
Two bad outcomes:
- Duplicate chart.
savefigtriggers an additional render that QRY also captures, producing two copies of the chart in the output. - Disk file leaked. The PNG sits in the executor's ephemeral working directory and is GC'd, but during the cell's lifetime it's there.
If you want the user to download the PNG, use qry.attach('chart.png') after the show — that handles the captured figure properly. But 99% of the time, just plt.show() is what you want.
Multiple subplots
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].plot(df['month'], df['revenue'])
axes[1].bar(df['region'], df['count'])
plt.tight_layout()
plt.show() # one show, both subplots captured
Works fine. One plt.show() per figure.
plotly
The rule: fig.write_html('chart.html')
import plotly.express as px
fig = px.line(df, x='month', y='revenue', title='Monthly revenue')
fig.write_html('chart.html') # captured inline, with fullscreen preview button
QRY detects chart.html (or any .html file written in the cell) and embeds it as an iframe with full Plotly interactivity preserved: hover tooltips, zoom, pan, legend toggling.
The fullscreen preview button (top-right of the embedded chart) lets the viewer expand the chart to take over the page — useful for dense visualisations.
Why not fig.show()?
fig.show() opens a browser window in interactive Python, which has no equivalent in the executor sandbox — it's silently a no-op or produces an error. Always write_html.
Multiple charts in one cell
fig1.write_html('revenue.html')
fig2.write_html('regions.html')
Both get captured, in declaration order.
Choosing matplotlib vs. plotly
| matplotlib | plotly | |
|---|---|---|
| Executor | Native (fast) | K8s (cold start) |
| Output type | PNG (static) | HTML (interactive) |
| Best for | Reports, fast iteration, small dashboards | Exploration, hover-rich detail, sharing |
| Cost | Cheap | Bigger output, slower |
For most analyses, matplotlib is fine. Switch to plotly when interactivity is the point: 50-line scatter plots, time series with hover, geo maps.
Charts produced by QRY without writing code
Most chat-driven analyses produce a chart without you writing the Python — QRY generates the code, runs it, captures the result. You can still steer the chart by saying so:
Plot it as a horizontal bar chart, sorted descending, with the
top 10 only.
Make this a line chart instead, with months on the X axis.
Use a stacked area chart, with category as the stack series.
The generated code respects matplotlib conventions (so no duplicates) by default.
Common issues
Two copies of the same chart.
You used both savefig and show. Drop savefig.
Chart doesn't render at all.
Either the cell errored before reaching plt.show() (read the error in Processing Details), or the figure is empty (data was filtered to zero rows). Check df.shape before plotting.
Plotly chart shows as plain text / source.
You didn't write to a .html file. fig.show() doesn't work; use fig.write_html('chart.html').
Chart looks tiny.
plt.figure(figsize=(width, height)) before plotting. Default is 6×4 inches; for dense data go 10×5 or larger.
Fonts in matplotlib charts look weird. The K8s executor has limited fonts. Stick to the matplotlib defaults; or for truly polished output, render in plotly which uses web fonts.
Chart needs to be saved for an email scheduled task. The scheduled task pipeline already captures the chart and inlines it as a PNG in the email body. You don't need to save explicitly.
See also
- Running Python — executor routing (matplotlib → native, plotly → K8s).
- Cell types and models — Python cells in notebooks.
- Python Executor matplotlib support — full reference.