Add Pie Chart Diagrams to a Layer in PyQGIS

A choropleth shows one value per area. Often the interesting story is a composition: the share of votes for each party in each constituency, the mix of land uses in each municipality, the split of commuting modes per district. QGIS can draw a small chart on every feature — a diagram — and the pie chart is the most familiar form for "parts of a whole". Configured carelessly, a map of pies is a cluttered mess; configured well, it shows patterns of composition that no other map type can.

This recipe belongs to Diagrams & Charts on Maps. It builds a pie diagram renderer from Python, sets categories, colours and labels, sizes the pies, places them sensibly, and explains when a different diagram serves better.

The objects behind a diagramA diagram on a vector layer is configured through four objects. QgsPieDiagram is the chart type. QgsDiagramSettings holds the categories, which are fields or expressions, their colours and labels, the size and outline. A diagram renderer, single category for fixed size or linearly interpolated for scaled size, combines type and settings. QgsDiagramLayerSettings controls placement, priority and visibility on the layer.Type, settings, renderer, placementQgsPieDiagramthe chart typeQgsDiagramSettingscategories, colourssize, outlinerendererfixed or scaledsizelayer settingsplacementpriorityone pie per feature,slices from attributes

Prerequisites

  • QGIS 3.34 LTR or newer, or the QGIS 4 series.
  • A polygon or point layer with several numeric fields that are parts of one whole — for example votes_a, votes_b, votes_c, or land-use areas per category.
  • Values in comparable units. A pie of a percentage and an absolute count is meaningless.

Build a pie diagram

The minimum configuration is a diagram type, settings with at least one category, a renderer, and layer settings. Assigning the renderer and layer settings to the layer makes the pies appear.

from qgis.core import (QgsProject, QgsPieDiagram, QgsDiagramSettings,
                       QgsSingleCategoryDiagramRenderer, QgsDiagramLayerSettings)
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtCore import QSizeF

districts = QgsProject.instance().mapLayersByName("districts")[0]

settings = QgsDiagramSettings()
settings.categoryAttributes = ['"walk"', '"cycle"', '"transit"', '"car"']
settings.categoryColors = [QColor("#15803d"), QColor("#0f766e"),
                           QColor("#2563eb"), QColor("#b45309")]
settings.categoryLabels = ["walk", "cycle", "transit", "car"]
settings.size = QSizeF(9, 9)                       # millimetres
settings.penColor = QColor("#ffffff")
settings.penWidth = 0.3
settings.enabled = True

renderer = QgsSingleCategoryDiagramRenderer()
renderer.setDiagram(QgsPieDiagram())
renderer.setDiagramSettings(settings)
districts.setDiagramRenderer(renderer)

layer_settings = QgsDiagramLayerSettings()
layer_settings.setPlacement(QgsDiagramLayerSettings.OverPoint)
districts.setDiagramLayerSettings(layer_settings)
districts.triggerRepaint()

Breakdown: categoryAttributes takes expressions, not bare field names — hence the double quotes around each field, which is how expressions reference fields. Colours and labels are parallel lists in the same order as the attributes; labels appear in the legend. Size is in millimetres by default, so a 9 mm pie stays the same size on screen at every zoom level. A thin white outline separates slices and keeps neighbouring pies distinct. OverPoint centres each pie on the feature's centroid or point.

Use expressions as categories

Categories do not have to be raw fields. Any expression works — combining fields, converting units, or computing shares — which keeps derived values out of the attribute table.

settings.categoryAttributes = [
    '"walk" + "cycle"',                 # active travel combined
    '"bus" + "tram" + "rail"',          # public transport combined
    '"car_driver" + "car_passenger"',
    'coalesce("other", 0)',
]
settings.categoryLabels = ["active", "public transport", "car", "other"]
settings.categoryColors = [QColor("#15803d"), QColor("#2563eb"),
                           QColor("#b45309"), QColor("#59645f")]
renderer.setDiagramSettings(settings)
districts.triggerRepaint()

Breakdown: Combining detailed fields into four or five broad groups is the single most effective way to make pies readable: beyond about five slices, small slices become indistinguishable. coalesce turns NULLs into zero so a missing value does not remove the whole pie — in an expression, any arithmetic involving NULL yields NULL. Because the expressions are stored in the layer's style, the grouping travels with a saved QML or project.

Fewer slices read betterTwo pies for the same district. The first has nine slices from detailed mode fields, several thinner than a few degrees and impossible to compare. The second groups them into four broad categories, each wide enough to see and compare with neighbouring pies.Group detailed fields into four or five slicesnine slices: unreadablefour groups: comparable

Choose colours that fit the map

Diagram colours compete with the base map and any fill colours underneath. Qualitative colours with similar lightness, an unobtrusive base layer and consistent colours for the same categories across all maps of a project make diagrams legible.

from qgis.core import QgsStyle

ramp = QgsStyle.defaultStyle().colorRamp("Set2")
n = len(settings.categoryAttributes)
settings.categoryColors = [ramp.color(i / max(n - 1, 1)) for i in range(n)]
renderer.setDiagramSettings(settings)

# a quiet base: light grey fill, thin outline
from qgis.core import QgsFillSymbol, QgsSingleSymbolRenderer
base = QgsFillSymbol.createSimple({"color": "#f0ebdd", "outline_color": "#b8b1a0",
                                   "outline_width": "0.2"})
districts.setRenderer(QgsSingleSymbolRenderer(base))
districts.triggerRepaint()

Breakdown: Sampling a qualitative ramp such as Set2 at evenly spaced positions gives distinct colours of similar weight, so no category dominates visually because of its hue. Putting the diagrams over a pale, neutral fill — rather than over a choropleth — keeps attention on the slices; a choropleth beneath pies forces readers to decode two colour schemes at once. If the same categories appear on other maps, reuse the exact colours, for example by storing them in a shared QML style as in saving and loading QML styles.

Size pies by a total

With fixed sizes, every pie shows composition only. Scaling pie area by a total — population, total votes — adds magnitude, so large districts are visible as large pies.

from qgis.core import QgsLinearlyInterpolatedDiagramRenderer

scaled = QgsLinearlyInterpolatedDiagramRenderer()
scaled.setDiagram(QgsPieDiagram())
scaled.setDiagramSettings(settings)
scaled.setClassificationAttributeExpression('"walk" + "cycle" + "transit" + "car"')
max_total = max(f["walk"] + f["cycle"] + f["transit"] + f["car"] for f in districts.getFeatures())
scaled.setLowerValue(0)
scaled.setUpperValue(max_total)
scaled.setLowerSize(QSizeF(0, 0))
scaled.setUpperSize(QSizeF(16, 16))
settings.scaleByArea = True
scaled.setDiagramSettings(settings)
districts.setDiagramRenderer(scaled)
districts.triggerRepaint()

Breakdown: The linearly interpolated renderer maps a value range to a size range. Starting both at zero means a district with half the maximum total gets half the area — but only with scaleByArea set, which interpolates area rather than diameter; scaling diameter makes large values look far bigger than they are. Sizing diagrams by attribute covers the choices and adds a size legend.

Place pies without clutter

Pies on small, dense polygons overlap. Layer settings control where each pie sits, whether overlapping ones are hidden, and which win when they collide.

Placement and collision optionsOver point places each pie on the feature's centroid or point. Around point offsets it at a set distance. Show all diagrams draws every pie even if they overlap, otherwise colliding diagrams are dropped by priority. Obstacle settings let labels avoid diagrams. A z-index orders diagrams relative to labels.Where pies go, and what happens when they collideplacementover pointaround pointdistance in mmcollisionsshow all, ordrop by priority1 to 10with labelsdiagrams as obstaclesz-index orderinglabels avoid pies

ls = QgsDiagramLayerSettings()
ls.setPlacement(QgsDiagramLayerSettings.OverPoint)
ls.setShowAllDiagrams(False)          # drop pies that would overlap others
ls.setPriority(5)
ls.setZIndex(1)
districts.setDiagramLayerSettings(ls)

settings.scaleBasedVisibility = True
settings.minimumScale = 250000        # hide when zoomed out beyond 1:250,000
settings.maximumScale = 0
renderer.setDiagramSettings(settings)
districts.triggerRepaint()

Breakdown: With setShowAllDiagrams(False), the labelling engine treats diagrams like labels: where two would overlap, the lower-priority one is not drawn. That keeps the map readable at the cost of some missing pies, which is why it is usually combined with scale-based visibility so pies only appear when there is room. On a layer with labels, the label engine also uses diagrams as obstacles, so names are moved off the pies instead of printed across them. Scale limits use the map scale denominator; minimumScale is the most zoomed-out scale at which pies still show.

Check the totals behind each pie

A pie implies that its slices make up a whole. If the category fields do not add up to the real total — because a minor category was left out, or because some respondents gave no answer — every pie silently misrepresents its district. A quick comparison against a total field catches this before the map is published.

gaps = []
for f in districts.getFeatures():
    parts = sum((f[n] or 0) for n in ("walk", "cycle", "transit", "car"))
    total = f["commuters_total"] or 0
    if total and abs(parts - total) / total > 0.02:
        gaps.append((f["district"], parts, total))
print(len(gaps), "districts where the slices miss more than 2 % of the total")

Breakdown: Comparing the sum of slice fields with an independent total, where the data has one, reveals missing categories and data errors. When the difference is real — people who work from home, for instance — add it as its own slice, computed in an expression such as "commuters_total" - ("walk" + "cycle" + "transit" + "car"), rather than letting the visible slices claim the whole. A pie whose slices are honest about "other" and "not stated" is always better than one that quietly rescales the rest.

When a pie is the wrong chart

Pies work for two to five parts of one whole, compared roughly. They are poor at precise comparison between features — people judge angles badly — and useless when the parts do not sum to a meaningful total.

  • To compare the same category across features, use histogram diagrams, where bar lengths are easy to compare.
  • To show composition and total together on a precise scale, use stacked bar diagrams.
  • To show one share, a choropleth of that percentage is clearer than any diagram.

QGIS version compatibility

The diagram API shown is stable across QGIS 3.34 LTR, 3.40 LTR and QGIS 4. On QGIS 4, enum members are scoped: QgsDiagramLayerSettings.Placement.OverPoint. QGIS 3.40 added QgsStackedDiagram for combining several diagrams per feature; it does not change the pie API. scaleByArea has existed since 3.0.

Troubleshooting

  • No pies appear. settings.enabled was not set, layer settings were never assigned, or the scale is outside the visibility range.
  • Pies are empty or missing on some features. A category expression evaluated to NULL; wrap fields in coalesce.
  • Large values dominate. Size is scaled by diameter; set scaleByArea = True.
  • Pies hide each other. Enable collision handling or raise the minimum visibility scale.

Conclusion

Build pies with QgsPieDiagram and QgsDiagramSettings, use expressions to group detail into four or five categories with NULL-safe arithmetic, choose qualitative colours over a quiet base, scale area by a total when magnitude matters, manage collisions with priorities and scale limits, and switch to bars when precise comparison is the point.

Frequently Asked Questions

Can pies be drawn in a print layout legend? Yes. Enable renderer.setAttributeLegend(True) and the categories appear in the layer's legend entry.

Can I rotate pies so the largest slice starts at the top? Set settings.rotationOffset (degrees) on recent releases; slice order follows the category order.

Do diagrams work on line layers? Yes; they are placed along or over the line according to the placement setting.

Can diagrams be exported to SVG or PDF? Yes. They render as vector graphics in layout exports.