API: Charts
Member reference for power_pptx.chart. For the narrative version — how the pieces fit together and which one to reach for — see Charts.
add_chart
ShapeTree.add_chart(chart_type, x, y, cx, cy, chart_data) returns theGraphicFrame shape, not the chart; reach the chart via .chart. The data is copied into an embedded XLSX workbook so PowerPoint's "Edit Data" works.
from power_pptx import Presentation
from power_pptx.chart.data import CategoryChartData, XyChartData, BubbleChartData
from power_pptx.enum.chart import XL_CHART_TYPE
from power_pptx.util import Inches
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
chart_data = CategoryChartData()
chart_data.categories = ["Q1", "Q2", "Q3", "Q4"]
chart_data.add_series("Revenue", (1.2, 1.8, 2.4, 3.1))
# add_chart(chart_type, x, y, cx, cy, chart_data) -> GraphicFrame
x, y, cx, cy = Inches(1), Inches(1), Inches(8), Inches(4.5)
chart = slide.shapes.add_chart(
XL_CHART_TYPE.COLUMN_CLUSTERED, x, y, cx, cy, chart_data
).chartChart data
CategoryChartData serves every category-based chart type,XyChartData serves XY_SCATTER* and BubbleChartDataserves BUBBLE*. ChartData is a deprecated alias ofCategoryChartData.
data = CategoryChartData(number_format="General")
data.categories = ["Q1", "Q2", "Q3"] # replaces the whole list
data.add_category("Q4") # append one; returns Category
data.categories.are_dates # date labels -> time-scale axis
data.categories.are_numeric
data.categories.depth # 1 flat, 2 with sub-categories
data.categories.leaf_count # values expected per series
data.categories.number_format = "General"
series = data.add_series("Revenue", (1.2, 1.8, 2.4), number_format=None)
series.add_data_point(3.1)
series.name, series.values, series.index
# Two-level categories: every leaf takes one value
nested = CategoryChartData()
emea = nested.add_category("EMEA") # Category
emea.add_sub_category("UK")
emea.add_sub_category("DE")
nested.add_series("Bookings", (4.1, 3.2))from power_pptx.chart.data import XyChartData, BubbleChartData
xy = XyChartData(number_format="General")
s = xy.add_series("Trial") # XySeriesData
s.add_data_point(1.0, 2.5) # (x, y)
s.x_values, s.y_values
bubbles = BubbleChartData()
b = bubbles.add_series("Accounts") # BubbleSeriesData
b.add_data_point(1.0, 2.0, 10) # (x, y, size)
b.bubble_sizesChart
chart.chart_type # XL_CHART_TYPE of the back-most plot
chart.shape # the containing GraphicFrame
chart.font # Font: chart-wide text defaults
chart.has_title # bool, read/write (non-destructive test)
chart.chart_title # ChartTitle; CREATES the title element
chart.chart_title.text_frame.text = "Revenue"
chart.chart_title.has_text_frame
chart.chart_title.format # ChartFormat (fill / line)
chart.plots # Sequence[_BasePlot], back-most first
chart.series # SeriesCollection across all plots
chart.value_axis # ValueAxis
chart.category_axis # CategoryAxis | DateAxis (ValueAxis on XY)
chart.secondary_value_axis # ValueAxis; CREATES it on first access
chart.has_legend = True # False removes the element and its settings
chart.legend # Legend | None
chart.chart_style = 10 # built-in style index 1..48
chart.replace_data(chart_data) # swap data, keep formattingPlot
A plot (a "chart group" in the Microsoft object model) is one run of series drawn in a single charting type. add_chart always produces exactly one; combo charts with two plots come from template decks. Concrete classes: AreaPlot, Area3DPlot,BarPlot, BubblePlot, DoughnutPlot,LinePlot, PiePlot, RadarPlot, XyPlot.
plot = chart.plots[0]
plot.chart # back-reference to the owning Chart
plot.categories # Categories (read back from the XML)
plot.series # SeriesCollection for this plot only
plot.has_data_labels = True
plot.data_labels # DataLabels
plot.vary_by_categories # colour each point differently
# This chart is COLUMN_CLUSTERED, so its plot is a BarPlot
plot.gap_width = 60 # % of bar width between category groups
plot.overlap = -10 # -100..100, series overlap within a groupMembers that exist only on particular plot classes:
| Member | Class | Range / meaning |
|---|---|---|
gap_width | BarPlot | Space between category groups, as a % of bar width (default 150) |
overlap | BarPlot | -100..100; how far series overlap within a group (default 0) |
hole_size | DoughnutPlot | 10..90, default 50 fork |
smooth | LinePlot | Curve-fit every series; reads True only when all are smoothed fork |
bubble_scale | BubblePlot | 0..300, % of default bubble size |
Series
Concrete classes: AreaSeries, BarSeries, LineSeries,PieSeries, RadarSeries, XySeries,BubbleSeries. chart.series lists every plot's series in draw order;plot.series narrows to one plot.
series = chart.series[0]
series.name, series.index
series.values # tuple of floats
series.format # ChartFormat -> .fill / .line
series.data_labels # DataLabels for this series
series.points # CategoryPoints / XyPoints / BubblePoints
# bar, line, scatter, area and bubble series only (fork)
series.trendlines # Trendlines
series.error_bars # ErrorBars
series.axis_group # "primary" | "secondary"
# BarSeries
series.invert_if_negative = True| Member | Class | Meaning |
|---|---|---|
invert_if_negative | BarSeries | Fill negative bars with the inverse colour |
smooth | LineSeries | Curve-fit this one series |
marker | LineSeries, RadarSeries, XySeries, BubbleSeries | Marker: style, size, format |
trendlines, error_bars, axis_group | Area, Bar, Line, Xy, Bubble | Not permitted on PieSeries / RadarSeries fork |
Points and markers
point = chart.series[0].points[0]
point.format # ChartFormat -> per-point fill / line
point.marker # Marker
point.data_label # DataLabel
from power_pptx.enum.chart import XL_MARKER_STYLE
marker = point.marker
marker.style = XL_MARKER_STYLE.CIRCLE
marker.size = 7 # 2..72 points
marker.format.fill.solid()Axes
chart.category_axis returns a DateAxis when the categories are dates, and the X ValueAxis on scatter/bubble charts. It raises ValueError on chart types with no axes (pie, doughnut), as does secondary_value_axis.
axis = chart.value_axis # or chart.category_axis
axis.visible = True
axis.has_major_gridlines = True
axis.has_minor_gridlines = False
axis.major_gridlines.format.line.color.rgb = "#EEEEF2"
axis.major_tick_mark # XL_TICK_MARK
axis.minor_tick_mark
axis.tick_label_position # XL_TICK_LABEL_POSITION
axis.tick_labels # TickLabels
axis.format # ChartFormat -> the axis line itself
axis.has_title = True
axis.axis_title.text_frame.text = "$M" # CREATES the title element
# ValueAxis only
axis.minimum_scale, axis.maximum_scale # None = auto
axis.major_unit, axis.minor_unit
axis.log_base = 10 # 2..1000; None restores linear (fork)
axis.crosses # XL_AXIS_CROSSES
axis.crosses_at
# CategoryAxis / DateAxis only
chart.category_axis.category_type # XL_CATEGORY_TYPE
chart.category_axis.reverse_order # BAR_* defaults True (fork)TickLabels
ticks = chart.value_axis.tick_labels
ticks.font # Font
ticks.number_format = "#,##0"
ticks.number_format_is_linked = False
# offset lives on the category axis only (0..1000, label distance)
chart.category_axis.tick_labels.offset = 100DataLabels and DataLabel
from power_pptx.enum.chart import XL_LABEL_POSITION
plot = chart.plots[0]
plot.has_data_labels = True # required: reading data_labels
# on a plot without them raises
labels = plot.data_labels # or chart.series[0].data_labels
labels.font
labels.number_format = "0.0%"
labels.number_format_is_linked = False
labels.position = XL_LABEL_POSITION.OUTSIDE_END
labels.show_value
labels.show_category_name
labels.show_series_name
labels.show_legend_key
labels.show_percentage
labels.collision_strategy = "auto" # fork; write-only
label = chart.series[0].points[0].data_label
label.font
label.position
label.has_text_frame = True
label.text_frame.text = "best quarter"Legend
from power_pptx.enum.chart import XL_LEGEND_POSITION
chart.has_legend = True
legend = chart.legend
legend.position = XL_LEGEND_POSITION.BOTTOM
legend.include_in_layout = False
legend.horz_offset = 0.0 # -1.0..1.0, only when position is CORNER
legend.fontTrendlines and error bars fork
Available on bar, line, scatter, area and bubble series only — the schema forbids them on pie and radar. Trendline kinds: "linear", "poly", "exp","log", "power", "movingAvg".
s = chart.series[0]
t = s.trendlines.add("linear", show_equation=True, show_r_squared=True)
s.trendlines.add("poly", order=3) # 2..6
s.trendlines.add("movingAvg", period=2)
t.trendline_type # XL_TRENDLINE_TYPE
t.name, t.forward, t.backward
s.error_bars.fixed(0.5)
s.error_bars.percentage(5)
s.error_bars.standard_deviation(1)
s.error_bars.standard_error()
s.error_bars.custom(plus=[0.1, 0.2], minus=[0.1, 0.3])
s.error_bars.exists, s.error_bars.include_type, s.error_bars.value
s.error_bars.remove()power_pptx.formats fork
Number-format string builders for anything that takes a number_format — axis tick labels, data labels, CategoryChartData(number_format=...), per-series formats and table cells. Each returns a plain Excel format str. decimals must be>= 0; a negative value raises ValueError.
| Call | Returns | Renders 1234.5 / 0.275 as |
|---|---|---|
currency() | "$"#,##0 | $1,235 |
currency("£", decimals=2) | "£"#,##0.00 | £1,234.50 |
currency("USD ") | "USD "#,##0 | USD 1,235 |
percent() | 0% | 28% |
percent(decimals=1) | 0.0% | 27.5% |
decimal() | #,##0.00 | 1,234.50 |
decimal(decimals=0, thousands_sep=False) | 0 | 1235 |
thousands() | #,##0 | 1,235 |
scientific() | 0.00E+00 | 1.23E+03 |
date() | yyyy-MM-dd | 2026-08-24 |
date("MMM YYYY") | MMM yyyy | Aug 2026 |
date(pattern) lowercases the tokens Excel wants lowercase (YYYY, YY, DD, DDD, DDDD,HH, H, SS, S) and deliberately leavesM/MM/MMM/MMMM capitalised, because lowercase m means minutes in Excel's grammar.
from power_pptx.formats import (
currency, percent, decimal, thousands, scientific, date
)
chart.value_axis.tick_labels.number_format = currency("$", decimals=0)
chart.value_axis.tick_labels.number_format_is_linked = False
chart.plots[0].has_data_labels = True
chart.plots[0].data_labels.number_format = percent(decimals=1)
chart.plots[0].data_labels.number_format_is_linked = FalseAssigning a format while number_format_is_linked is True has no visible effect — PowerPoint re-reads the format from the embedded workbook cell. Set the flag to False alongside every assignment.
Palettes & quick layouts fork
from power_pptx.chart import palettes, quick_layouts
chart.recolour("vibrant") # fork: one call, dispatches per type
chart.recolour("modern", by="series") # or by="category" / "auto"
chart.recolor("modern") # US-spelling alias
chart.apply_palette("modern") # warns + reroutes on pie/doughnut
chart.color_by_category("modern") # per-point recolour
chart.apply_dark_theme(text="#E5E7EB", line="#374151") # fork
chart.text_color = "#FFFFFF" # fork; write-only
chart.line_color = "#9CA3AF" # fork; write-only
chart.apply_quick_layout("minimal") # 10 presets, or a dict spec
palettes.palette_names() # modern, classic, editorial, vibrant, ...
palettes.resolve_palette("editorial") # list[RGBColor]
palettes.CHART_PALETTES # the mapping itself
quick_layouts.layout_names() # title_legend_right, minimal, dense, ...Chart types
XL_CHART_TYPE covers the full OOXML set in 73 members: COLUMN_*,BAR_*, LINE*, PIE*, DOUGHNUT*,AREA*, XY_SCATTER*, BUBBLE*, RADAR*,STOCK_*, SURFACE*, the THREE_D_* variants and theCONE_* / CYLINDER_* / PYRAMID_* shapes. The complete member list is on API: Enumerations, along with every other chart enum (XL_LEGEND_POSITION, XL_LABEL_POSITION,XL_TICK_MARK, XL_MARKER_STYLE, XL_TRENDLINE_TYPE, …).