Export an Excel Chart as an Image with Python
Charts live in workbooks, but people want them elsewhere: in the body of the weekly email, on a slide, in a dashboard page or a PDF summary. openpyxl and XlsxWriter only write a chart's definition — series, colours, axes — and leave the drawing to whatever application opens the file. To get a picture, something has to render it. This guide, part of Inserting Images and Logos into Excel, covers the three practical routes: let Excel render it through COM, let LibreOffice render it on a server, or redraw the same chart from the same data with matplotlib.
Prerequisites
pip install openpyxl pillow
LibreOffice (soffice) for the server route, pdftoppm from poppler for high-resolution output, and pywin32 plus Excel for the COM route. A workbook with data on one sheet:
import subprocess
from openpyxl import Workbook
from openpyxl.chart import BarChart, Reference
from PIL import Image, ImageChops
def build_chart(ws_data):
chart = BarChart()
chart.x_axis.delete = False # Excel hides axes unless this is set
chart.y_axis.delete = False
chart.add_data(Reference(ws_data, min_col=2, min_row=1, max_row=5), titles_from_data=True)
chart.set_categories(Reference(ws_data, min_col=1, min_row=2, max_row=5))
chart.title = "Revenue"
chart.width, chart.height = 16, 9 # centimetres
return chart
Route 1 — Excel renders it (Windows)
With Excel installed, ask it for the picture. Chart.Export writes the chart exactly as it appears in the workbook, fonts and theme included:
import win32com.client as win32
excel = win32.DispatchEx("Excel.Application")
excel.Visible = False
try:
wb = excel.Workbooks.Open(r"C:\reports\sales.xlsx", ReadOnly=True)
ws = wb.Worksheets("Summary")
for i in range(1, ws.ChartObjects().Count + 1):
chart_object = ws.ChartObjects(i)
chart_object.Chart.Export(rf"C:\reports\chart_{i}.png")
wb.Close(SaveChanges=False)
finally:
excel.Quit()
Paths must be absolute, because Excel resolves them from its own working directory. The image size follows the chart object's size on the sheet; enlarge chart_object.Width and Height before exporting for a larger picture. Driving Excel through COM, including running it from scheduled tasks, is covered in Run Excel COM Automation from Task Scheduler.
Route 2 — LibreOffice renders it (any server)
LibreOffice has no "export this chart" command, but it can render the first sheet of a workbook to PNG or PDF. So build a small workbook whose first sheet holds nothing but the chart, anchored at A1, with its data on a second sheet:
wb = Workbook()
chart_sheet = wb.active
chart_sheet.title = "ChartOnly"
data = wb.create_sheet("Data")
data.append(["Month", "Revenue"])
for row in [("Jan", 82), ("Feb", 91), ("Mar", 78), ("Apr", 104)]:
data.append(row)
chart_sheet.add_chart(build_chart(data), "A1")
wb.save("chart_only.xlsx")
subprocess.run(["soffice", "--headless", "--convert-to", "png", "chart_only.xlsx"],
check=True, capture_output=True, timeout=120)
The PNG is a whole page: the chart in the top-left corner and white space around it. Crop it to the chart with Pillow by finding the bounding box of everything that is not white:
def crop_white(path, out, margin=4):
picture = Image.open(path).convert("RGB")
background = Image.new("RGB", picture.size, (255, 255, 255))
left, top, right, bottom = ImageChops.difference(picture, background).getbbox()
picture.crop((max(left - margin, 0), max(top - margin, 0), right + margin, bottom + margin)).save(out)
return Image.open(out).size
print(crop_white("chart_only.png", "chart.png")) # (614, 349) on our system
On our system the page rendered at screen resolution and the cropped chart came out at roughly 600 × 340 pixels — fine for an email, soft on a slide. A chart on a chartsheet, by contrast, rendered as a blank page, which is why the chart goes on an ordinary worksheet here.
Route 2b — Higher resolution through PDF
For slides and print, render to PDF and rasterise at the resolution you need with pdftoppm, then crop as before:
subprocess.run(["soffice", "--headless", "--convert-to", "pdf", "chart_only.xlsx"],
check=True, capture_output=True, timeout=120)
subprocess.run(["pdftoppm", "-f", "1", "-l", "1", "-r", "200", "-png", "chart_only.pdf", "page"],
check=True, timeout=120)
print(crop_white("page-1.png", "chart_hd.png")) # (1268, 717)
At 200 dots per inch the same chart comes out at about 1,250 × 700 pixels with crisp text, because the PDF keeps the chart as vector graphics until pdftoppm draws it. Set -r 300 for print; the rendering time grows with the resolution, but for a single chart it stays well under a second. The PDF itself is also a good deliverable when the chart goes into a document: it scales without losing quality.
Route 2c — Every chart in an existing workbook
Reports usually arrive with several charts already on a sheet. openpyxl reads existing charts back into ws._charts, so each one can be moved onto its own render page: load the workbook, take one chart off its sheet, anchor it at A1 of a new first sheet, save a copy and render that:
from openpyxl import load_workbook
def export_charts(path, sheet_name, prefix="chart", size_cm=(16, 9)):
count = len(load_workbook(path)[sheet_name]._charts)
sizes = []
for i in range(count):
wb = load_workbook(path)
chart = wb[sheet_name]._charts[i]
wb[sheet_name]._charts = [] # keep only the chart being exported
chart.width, chart.height = size_cm # reloaded charts use the default size
page = wb.create_sheet("Render", 0)
page.add_chart(chart, "A1")
wb.active = 0
wb.save(f"{prefix}_{i}.xlsx")
subprocess.run(["soffice", "--headless", "--convert-to", "png", f"{prefix}_{i}.xlsx"],
check=True, capture_output=True, timeout=120)
sizes.append(crop_white(f"{prefix}_{i}.png", f"{prefix}_{i}_cropped.png"))
return sizes
print(export_charts("chart_only.xlsx", "ChartOnly")) # [(614, 349)]
Loading a fresh copy for each chart keeps the exports independent. A reloaded chart comes back at openpyxl's default size rather than the size it had in the workbook, so the function sets the size explicitly — without that line the same chart rendered noticeably smaller in our test. Tried on a workbook with three styled charts — stacked columns with labels, a 100% stack and horizontal bars — each came out as its own image with colours, labels and legends intact. openpyxl models most chart features, but anything it does not understand is dropped when it reloads a chart, so compare the exports with the workbook once when adopting this for a new report.
Route 3 — Redraw it with matplotlib
When every image must look identical regardless of which office suite is installed, or when you produce hundreds of charts, skip the workbook renderer and draw the same chart from the same data:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
months, revenue = ["Jan", "Feb", "Mar", "Apr"], [82, 91, 78, 104]
fig, ax = plt.subplots(figsize=(6.4, 3.6), dpi=200)
ax.bar(months, revenue, color="#4338CA")
ax.set_title("Revenue")
ax.spines[["top", "right"]].set_visible(False)
fig.tight_layout()
fig.savefig("chart_mpl.png")
plt.close(fig)
print(Image.open("chart_mpl.png").size) # (1280, 720)
The image is fast, reproducible and exactly as styled — but it is a second chart definition that must be kept in step with the one in the workbook. Put the data and styling in one function used for both when the two must match. Going the other way — putting a matplotlib chart into a workbook — is covered in Embed a Matplotlib Chart in an Excel Report.
Common pitfalls
- Charts on chartsheets. They rendered as blank pages through LibreOffice. Use a worksheet with the chart at A1.
- Several charts on one sheet. The page render includes all of them. Build one chart-only workbook per image, or crop by position.
- Rendering in parallel. Two
soffice --headlessprocesses sharing one user profile can block each other. Render files one after another, or give each worker its own profile with-env:UserInstallation=file:///tmp/lo_profile_N. - Leftover render files. Every export leaves a helper workbook and a full-page PNG behind. Write them to a temporary directory and delete it when the images are done.
- Fonts on servers. LibreOffice substitutes fonts that are not installed, which changes text widths. Install the report's fonts on the render machine.
Conclusion
openpyxl only describes charts, so an image needs a renderer. On Windows with Excel, Chart.Export through COM gives an exact picture. On a server, put the chart alone at A1 of a workbook's first sheet, convert it with headless LibreOffice — to PNG for speed or to PDF plus pdftoppm for resolution — and crop the page margins with Pillow. When images must be identical everywhere or produced at scale, redraw the chart from the same data with matplotlib.
Frequently asked questions
Can openpyxl save a chart as an image? No. openpyxl writes the chart's definition into the workbook but never draws it. A program that renders charts — Excel, LibreOffice — has to produce the picture, or you redraw the chart from the data with a plotting library.
How do I export a chart to PNG with Excel installed? Open the workbook with win32com and call Export on the chart, for example ws.ChartObjects(1).Chart.Export with the full path of the PNG to write. Excel renders it exactly as it appears in the workbook.
How do I export a chart on a Linux server? Put the chart alone on the first sheet of a workbook, convert that workbook with headless LibreOffice to PNG or PDF, and crop the white page margins with Pillow. The PDF route with pdftoppm gives a higher resolution.
Why is the exported image blank? A chart on a chartsheet rendered as an empty page in LibreOffice in our test. Place the chart on an ordinary worksheet at A1 instead, with its data on another sheet.
Related
- Up one level: Inserting Images and Logos into Excel — pictures in reports.
- Insert an Image from Memory into Excel with Python — putting images back into workbooks.
- Convert Excel to PDF on Linux with LibreOffice — the PDF route for whole sheets.
- Create a Bar Chart in Excel with openpyxl — building the chart first.
More on charts & images
- How-toBuild a Dashboard Sheet with Charts from Multiple Tabs
- How-toBuild a Rolling 12-Month Chart on an Excel Dashboard
- How-toKeep Charts and Images When Filling an Excel Template
- How-toAdd a Chart to an Excel File with xlsxwriter
- How-toAdd Sparklines to an Excel Report with xlsxwriter
- How-toBuild a Waterfall Chart in Excel with Python