# How to make charts with a code interpreter Run matplotlib code with `sbx.interpreter.run()`; every open figure comes back as a PNG in the result, with no file to fetch. **On Runtime the chart libraries are already in the sandbox.** The default image included NumPy 1.26.4, pandas 2.1.4 and matplotlib 3.6.3 on 24 September 2026, when PNG plotting was checked in a live sandbox ([changelog](/docs/changelog)), so a cell that draws a chart needs no install step. The interpreter keeps variables between cells like a notebook, runs in its own Firecracker microVM, and speaks seven languages: Python, JavaScript, TypeScript, R, Java, Bash and Go. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while it waits for the next cell ([pricing](/docs/pricing)). ## Draw a chart and save it ```ts check import { writeFile } from "node:fs/promises"; import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.interpreter.run(` import pandas as pd sales = pd.DataFrame({"month": ["Jan", "Feb", "Mar", "Apr"], "revenue": [120, 135, 160, 152]}) `); const cell = await sbx.interpreter.run(` import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(6, 3.5)) ax.bar(sales["month"], sales["revenue"], color="#3b6ea5") ax.set_title("Revenue by month") `); for (const [i, result] of cell.results.entries()) { const inline = result.data["image/png"]; const ref = result.refs["image/png"]; if (typeof inline === "string") await writeFile(`chart-${i}.png`, Buffer.from(inline, "base64")); else if (ref) await writeFile(`chart-${i}.png`, await sbx.interpreter.result(ref)); } ``` ```python check import base64 from withruntime import Sandbox with Sandbox.create() as sbx: sbx.interpreter.run( "import pandas as pd\n" "sales = pd.DataFrame({'month': ['Jan', 'Feb', 'Mar', 'Apr'], 'revenue': [120, 135, 160, 152]})" ) cell = sbx.interpreter.run( "import matplotlib.pyplot as plt\n" "fig, ax = plt.subplots(figsize=(6, 3.5))\n" "ax.bar(sales['month'], sales['revenue'], color='#3b6ea5')\n" "ax.set_title('Revenue by month')" ) for i, result in enumerate(cell["results"]): if "image/png" in result["data"]: png = base64.b64decode(result["data"]["image/png"]) elif "image/png" in result["refs"]: png = sbx.interpreter.result(result["refs"]["image/png"]) else: continue with open(f"chart-{i}.png", "wb") as file: file.write(png) ``` ```bash no-run runtime sandbox run-code "${id}" analysis.py --out-dir charts # each chart saved as a PNG ``` The second cell uses `sales` from the first: variables persist in the language's context until you restart it. You do not call `plt.show()` or `savefig`; figures still open when the cell ends are returned as PNG images and then closed, so the next cell starts with none. ## What a cell returns | Field | What it holds | | ------------------------------------------------------ | ---------------------------------------------------------------------------- | | `results[].data["image/png"]` | A chart, base64-encoded PNG | | `results[].refs["image/png"]` | A result too large to travel inline; fetch it with `interpreter.result(ref)` | | `results[].data["text/plain"]` | The value of the cell's last expression, as text | | `results[].data["application/vnd.runtime.table+json"]` | A pandas or R data frame, as a table | | `stdout`, `stderr` | What the cell printed | | `error` | `name`, `value` and `traceback` when the cell raised | | `status` | `ok`, `error`, `interrupted`, `timeout` or `lost` | A chart and a table can come back from the same cell, so a chatbot can show the picture and let the user sort the numbers. ## Other formats and languages **SVG or a PDF.** Save the figure yourself and read the file back: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.interpreter.run(` import matplotlib.pyplot as plt plt.plot([1, 4, 9, 16]) plt.savefig("/workspace/line.svg") plt.close() `); const svg = await sbx.files.readText("/workspace/line.svg"); console.log(svg.slice(0, 60)); ``` **R.** Base R plots come back as PNG in the same way: ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create({ timeoutSeconds: 900 }); const cell = await sbx.interpreter.run("plot(pressure, type = 'b')", { language: "r", timeoutMs: 300_000, }); console.log(cell.results.length, cell.status); ``` R installs from Ubuntu's archive the first time a sandbox uses it, which took about 90 seconds on 23 September 2026. Bake it into a [custom image](/docs/images) with `apt: ["r-base-core"]`, the package the interpreter installs, to skip that. **Other Python libraries.** A library the image does not list, such as seaborn, installs with `sbx.exec("pip install seaborn")`, or goes in an image recipe's `pip` list so every sandbox starts with it ([what is installed](/docs/sandbox-environment#what-is-installed)). ## Mistakes and how Runtime handles them - **Looking for the chart in `stdout`.** Charts arrive in `results`, not as printed text. Printing the figure object shows only its description. - **Charts that pile up.** Each cell's open figures are closed once returned, so a loop of cells never redraws old charts. - **A cell that hangs.** The default timeout is 60 seconds. A timeout is a result with `status: "timeout"`, not an exception, and the context keeps its variables. - **Model code you do not trust.** The cell runs in a microVM with its own kernel. Turn the internet off for it with `sbx.network.off()` ([how to](/how-to/turn-off-sandbox-internet)). ## Where this helps - A [data analysis agent](/use-cases/data-analysis-agent) that answers with a chart. - [A code interpreter for a chatbot](/use-cases/code-interpreter-for-chatbots), where users upload a CSV and ask for a plot. - [What is a code interpreter?](/glossary/code-interpreter) for the idea in general. An agent connected to Runtime's MCP server gets the same through `runtime_sandbox_interpreter_run`, with charts as images in the tool result. ## Start ```bash no-run npx withruntime sandbox run --trial -- python3 -c 'import matplotlib; print(matplotlib.__version__)' ``` New accounts get 50 free sandbox hours, no card. The first run prints a link to approve in your browser. Facts on this page were checked on 25 September 2026.