Skip to main content

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:

  1. Duplicate chart. savefig triggers an additional render that QRY also captures, producing two copies of the chart in the output.
  2. 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

matplotlibplotly
ExecutorNative (fast)K8s (cold start)
Output typePNG (static)HTML (interactive)
Best forReports, fast iteration, small dashboardsExploration, hover-rich detail, sharing
CostCheapBigger 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

QRYA product of IXEN.