How-to
Formatting And Charting Excel Reports With PythonDeep dive

Put a Chart on Its Own Chartsheet with openpyxl

Give a chart a whole sheet to itself — a full-screen, full-page chart tab that opens first in the workbook, prints on one page and cannot be dragged out of place. Create chartsheets with openpyxl, size the chart so every application shows it properly, make one per region in a loop, colour and order the tabs, protect them and set landscape printing.

A chart placed on a worksheet shares the grid with data: it covers cells, moves when rows are resized, and prints across page breaks unless someone lays it out carefully. Excel has a second kind of sheet for exactly this — the chartsheet, a tab that holds one chart and nothing else. The chart fills the window at any screen size, cannot be dragged off its data, and prints as a single full page. It is the natural home for the headline chart of a report: put it first, and the workbook opens on the picture, with the numbers a tab away. This page, part of Creating Charts in Excel with openpyxl, creates chartsheets with openpyxl and fixes the one default that makes them look empty outside Excel.

A chart on a worksheet versus a chartsheet Two panels. An embedded chart sits over cells on a worksheet: it can cover data, moves with row and column changes and needs page layout to print well, but it can sit beside the numbers. A chartsheet holds one chart only: it fills the window, cannot be moved off its place and prints as one page, while its data lives on another sheet. Chart on a worksheet sits beside its numbers can cover cells needs print layout Chartsheet fills the whole window prints as one page data lives on another tab

Prerequisites

Bash
pip install openpyxl

The examples chart twelve months of revenue for three regions; the data goes on a worksheet named Data.

Step 1 — Create a chartsheet and give it a chart

wb.create_chartsheet(title, index) adds a chartsheet at the given position. Build the chart exactly as for a worksheet — its references point at the Data sheet — and attach it with add_chart. Unlike a worksheet, a chartsheet takes no cell anchor, because the chart occupies the whole sheet:

Python
from openpyxl import Workbook
from openpyxl.chart import LineChart, Reference
from openpyxl.drawing.xdr import XDRPositiveSize2D
from openpyxl.utils.units import cm_to_EMU

months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
regions = {"North": [42, 45, 48, 51, 55, 58, 54, 52, 60, 63, 66, 72],
           "South": [30, 31, 35, 34, 38, 41, 44, 47, 45, 49, 52, 55],
           "East": [12, 14, 15, 18, 20, 22, 25, 27, 30, 33, 35, 40]}

wb = Workbook(); data = wb.active; data.title = "Data"
data.append(["Month", *regions])
for i, m in enumerate(months):
    data.append([m, *(v[i] for v in regions.values())])

def revenue_chart(title, first_col, last_col):
    chart = LineChart()
    chart.title = title
    chart.add_data(Reference(data, min_col=first_col, max_col=last_col, min_row=1, max_row=13), titles_from_data=True)
    chart.set_categories(Reference(data, min_col=1, min_row=2, max_row=13))
    chart.y_axis.title = "Revenue (k)"
    chart.x_axis.delete = False
    chart.y_axis.delete = False
    return chart

overview = wb.create_chartsheet("Overview", 0)
chart = revenue_chart("Revenue by region, 2026", 2, 4)
overview.add_chart(chart)
chart.anchor.ext = XDRPositiveSize2D(cm_to_EMU(25), cm_to_EMU(17))
print(wb.sheetnames, type(chart.anchor).__name__)

Index 0 puts Overview first, so the workbook's first tab is the chart. The last line matters outside Excel: openpyxl records the chart's size on a chartsheet as zero. Excel ignores it and stretches the chart to fill the sheet, but LibreOffice and other applications draw the chart at its stored size — a dot in the corner of an empty page. Setting the anchor's extent to a real size, in EMUs (English Metric Units, 360,000 per centimetre), makes the sheet look right everywhere; Excel still scales it to the window. The delete = False lines keep the axes visible in Excel, as explained in Format Chart Axes with openpyxl.

Step 2 — One chartsheet per region

Chartsheets suit a set of parallel charts — one per region, product or department — each a tab of its own that readers flip through. Create them in a loop, after the overview:

Python
for position, (name, col) in enumerate(zip(regions, range(2, 5)), start=1):
    sheet = wb.create_chartsheet(f"{name} chart", position)
    region_chart = revenue_chart(f"{name} revenue, 2026", col, col)
    region_chart.legend = None
    sheet.add_chart(region_chart)
    region_chart.anchor.ext = XDRPositiveSize2D(cm_to_EMU(25), cm_to_EMU(17))
print(wb.sheetnames)
print("chartsheets:", [s.title for s in wb.chartsheets], "| worksheets:", [s.title for s in wb.worksheets])

The four chart tabs come first and Data last. wb.chartsheets and wb.worksheets list the two kinds separately, which matters when a later step loops over "all sheets" to format cells — a chartsheet has no cells, and chartsheet["A1"] raises a TypeError. A chartsheet is meant for exactly one chart. openpyxl will accept a second add_chart call, but both charts are anchored at the same corner and drawn on top of each other, so make a new sheet per chart.

The workbook's tabs, chart first The order of the tabs in the finished workbook. First the Overview chartsheet with all three regions, then one chartsheet each for North, South and East, and last the Data worksheet that every chart references. Data worksheet — the numbers every chart uses North, South, East chart one chartsheet per region Overview first tab — opens on the picture

Step 3 — Colour the tabs, set printing and protect

Chartsheets have their own property, protection and page-setup objects, which start empty in openpyxl and are created rather than edited:

Python
from openpyxl.chartsheet.properties import ChartsheetProperties
from openpyxl.chartsheet.protection import ChartsheetProtection
from openpyxl.styles.colors import Color
from openpyxl.worksheet.page import PrintPageSetup

for sheet in wb.chartsheets:
    sheet.sheetPr = ChartsheetProperties(tabColor=Color("0B6157" if sheet.title == "Overview" else "5B6780"))
    sheet.pageSetup = PrintPageSetup(orientation="landscape", paperSize=9)
    sheet.sheetProtection = ChartsheetProtection(content=True)
wb.active = 0
wb.save("revenue_chartsheets.xlsx")

from openpyxl import load_workbook
check = load_workbook("revenue_chartsheets.xlsx")
print([(s.title, s.sheetProtection.content) for s in check.chartsheets], "| opens on:", check.active.title)

paperSize=9 is A4 (use 1 for US Letter); landscape suits most charts. Protecting the content stops readers changing the chart's formatting by accident while leaving the data sheet editable — add password=... to ChartsheetProtection for a light deterrent, remembering that sheet passwords are not security. wb.active = 0 makes the workbook open on the Overview tab.

Step 4 — Read and update chartsheets later

openpyxl reads chartsheets and the charts on them back, so a template workbook with chart tabs can be refilled: write new numbers to the Data sheet and save, and every chart updates because it references cells, not copies of values:

Python
wb2 = load_workbook("revenue_chartsheets.xlsx")
ws = wb2["Data"]
for row in ws.iter_rows(min_row=2, max_row=13, min_col=2, max_col=4):
    for cell in row:
        cell.value = round(cell.value * 1.1)
wb2.save("revenue_chartsheets_updated.xlsx")
print([len(s._charts) for s in load_workbook("revenue_chartsheets_updated.xlsx").chartsheets], "chart per chartsheet after saving")

Each chartsheet still carries its chart after the round trip, now showing the updated figures. Changing the range a chart covers — adding a thirteenth month — means rebuilding its references, which is easiest done by recreating the chart. For the same result with xlsxwriter, which writes new files only, use workbook.add_chartsheet() and chartsheet.set_chart(chart); see Add a Chart to an Excel File with xlsxwriter.

Chartsheet settings in openpyxl A table of chartsheet settings. create_chartsheet with a title and index adds and positions the sheet. add_chart attaches its single chart. anchor.ext sets the chart size for applications other than Excel. sheetPr with ChartsheetProperties sets the tab colour. pageSetup with PrintPageSetup sets orientation and paper. sheetProtection with ChartsheetProtection locks the chart. openpyxl call Purpose Add sheet wb.create_chartsheet(title, i) position in the tabs Attach chart sheet.add_chart(chart) one chart per sheet Size chart.anchor.ext shows outside Excel Tab colour ChartsheetProperties(tabColor=) group the tabs Printing PrintPageSetup(orientation=) landscape page Protection ChartsheetProtection(content=True) no accidental edits

When a chartsheet is the right choice

Chartsheets work best for a small number of headline charts that people look at rather than work with: the chart a manager opens the workbook to see, a set of parallel region charts to flip through, a page to print for a meeting. They are the wrong choice when readers need the chart and its numbers side by side, or several small charts compared on one screen — a dashboard sheet with embedded charts does that better, as Building Multi-Sheet Excel Dashboards shows. Many reports use both: a chartsheet first for the headline, then a dashboard worksheet, then the data. Name chart tabs so their purpose is obvious from the tab bar — "Overview" and "North chart" rather than "Chart1" — because a chartsheet has no cells to hold a heading.

Common pitfalls

  • Empty-looking chartsheets outside Excel. Set chart.anchor.ext after add_chart.
  • Missing axes in Excel. Set delete = False on both axes, as for any openpyxl chart.
  • Editing sheet properties that are None. Chartsheet properties, protection and page setup must be created, not modified.
  • Looping over every sheet to format cells. Use wb.worksheets; chartsheets have no cells.
  • Several charts on one chartsheet. They overlap at the same corner; create another sheet for the next chart.
  • Data on the same tab. A chartsheet cannot hold data; reference a worksheet.

Performance notes

Chartsheets add a sheet, a drawing and a chart part each — a few kilobytes. Dozens of chart tabs are fine; hundreds make the tab bar unusable before they make the file slow.

Conclusion

Create a chartsheet with wb.create_chartsheet(title, index), build its chart from references to a data worksheet, attach it with add_chart, and set chart.anchor.ext so applications other than Excel draw it at full size. Loop to make one chartsheet per group, set tab colours, landscape printing and protection by assigning new property objects, and make the headline chart the active first tab. Refill the data sheet later and the chartsheets follow.

Frequently asked questions

What is a chartsheet in Excel? A sheet that holds a single chart and no cells. The chart fills the window, stays in place, and prints on a full page. Its data lives on an ordinary worksheet elsewhere in the workbook.

How do I create a chartsheet with openpyxl? Call wb.create_chartsheet("Title", index) to add the sheet, build the chart from a Reference to data on a worksheet, and attach it with chartsheet.add_chart(chart). A chartsheet holds exactly one chart.

Why does my openpyxl chartsheet look empty in LibreOffice? openpyxl writes the chart's size on a chartsheet as zero. Excel stretches the chart to fit the sheet anyway, but other applications draw it at its stored size. Set chart.anchor.ext to a real size after add_chart so every application shows it.

Can I read or write cells on a chartsheet? No. A chartsheet has no cells, so chartsheet"A1" raises a TypeError. Keep the data on a worksheet, and use wb.chartsheets to tell the two kinds of sheet apart when looping over a workbook.

More on charts & images

More on charts & images →