Diagrams & Charts on Maps with PyQGIS
Most thematic maps show one value per feature: a colour for population density, a symbol size for the number of jobs. Many questions need more than one value at once. What is the mix of transport modes in each district? How do votes split between parties in each constituency, and how many were cast? How has a station's rainfall varied across the year? Answering them on a map means drawing a small chart at every feature — a diagram — or placing a full chart beside the map in a layout.
This guide belongs to PyQGIS Cartography & Data Visualization. It is for anyone producing thematic maps from Python who needs to show composition, comparison or change alongside location: analysts writing reports, cartographers automating map series, and developers building map-producing tools. It explains how QGIS's diagram system is put together, which diagram fits which question, how to size diagrams honestly, and how to add proper charts to print layouts.
What this guide covers
- Add pie chart diagrams to a layer for parts of a whole, with grouped categories, matching colours and clean placement.
- Add histogram bar diagrams on one shared scale so bars compare across the map.
- Add stacked bar diagrams to show totals and composition together, and combine diagrams per feature.
- Size diagrams by attribute with area scaling, outlier handling and one scale across a series.
- Add diagram legends and text diagrams so readers can decode colours and sizes, or read values directly.
- Add a chart to a print layout with matplotlib, per atlas page, or with the native chart item.
How the diagram system fits together
Diagrams are a property of a vector layer, separate from its symbology and labels. Four objects configure them, and every recipe in this guide uses the same four.
The diagram type — QgsPieDiagram, QgsHistogramDiagram, QgsStackedBarDiagram, QgsTextDiagram, and on QGIS 3.40 and newer the container QgsStackedDiagram — decides how values are drawn.
The diagram settings (QgsDiagramSettings) hold the categories, which are expressions, along with their colours and labels, plus size, outline, font, background, orientation and visibility scales.
The diagram renderer combines type and settings and decides size. QgsSingleCategoryDiagramRenderer draws every diagram at the same size; QgsLinearlyInterpolatedDiagramRenderer maps a value — a field or expression — to a size range.
The diagram layer settings (QgsDiagramLayerSettings) decide placement, priority, z-order and whether colliding diagrams are hidden, through the same labelling engine that places labels.
from qgis.core import (QgsProject, QgsPieDiagram, QgsDiagramSettings,
QgsSingleCategoryDiagramRenderer, QgsDiagramLayerSettings)
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtCore import QSizeF
layer = QgsProject.instance().mapLayersByName("districts")[0]
s = QgsDiagramSettings()
s.categoryAttributes = ['"walk" + "cycle"', '"bus" + "rail"', '"car"']
s.categoryColors = [QColor("#15803d"), QColor("#2563eb"), QColor("#b45309")]
s.categoryLabels = ["active", "public transport", "car"]
s.size = QSizeF(10, 10)
s.enabled = True
r = QgsSingleCategoryDiagramRenderer()
r.setDiagram(QgsPieDiagram())
r.setDiagramSettings(s)
layer.setDiagramRenderer(r)
layer.setDiagramLayerSettings(QgsDiagramLayerSettings())
layer.triggerRepaint()
Breakdown: Categories are expressions, so fields go in double quotes and can be combined freely — grouping detailed categories into a few broad ones is usually the first step to a readable diagram. Colours and labels are lists in the same order. Sizes default to millimetres, so diagrams keep their size as the map zooms. Swapping QgsPieDiagram for QgsHistogramDiagram or QgsStackedBarDiagram changes the chart type with the rest of the configuration unchanged.
Choosing the diagram type
The choice follows from what readers should take away.
Pies suit parts of a whole with two to five categories, compared roughly. People judge angles and areas poorly, so pies are weak for precise comparisons between features, and they become unreadable with many thin slices. Grouping categories in the expressions is the most effective improvement.
Histogram bars suit comparison. Bar length is the most accurately perceived visual variable, so bars let readers compare the same category between features — industrial jobs here versus there — provided every diagram uses the same scale. That shared scale is the key setting for this type.
Stacked bars suit totals and composition together: one bar per feature, its length the total, its segments the parts. Only the first segment shares a baseline across bars, so the category of most interest should go first.
Text diagrams suit cases where the numbers themselves are the message — turnout, counts, rates — printed inside a simple shape and placed by the diagram engine along with any other diagrams.
When none of these fits — too many categories, a time series, a distribution — the answer is usually a full chart beside the map rather than a small one on every feature.
Size: the most powerful and most abused variable
Sizing diagrams by an attribute adds magnitude: large districts get large pies. It is also where diagram maps most often mislead.
Three rules keep sizing honest. Scale from zero, so proportions are preserved; a non-zero minimum size exaggerates differences among small features. For pies and other two-dimensional diagrams, scale area rather than diameter; QGIS's scaleByArea setting does exactly that. And when several maps will be compared, compute one upper value over all of them. Sizing diagrams by attribute also deals with outliers — the capital city that shrinks everything else — through capping, transformed scales or an inset map, each of which must be stated in the legend.
Placement, collisions and labels
A map of diagrams is only useful if the diagrams do not pile up on each other. QGIS places diagrams with its labelling engine, which brings the same tools that keep labels tidy.
ls = QgsDiagramLayerSettings()
ls.setPlacement(QgsDiagramLayerSettings.OverPoint)
ls.setShowAllDiagrams(False) # drop colliding diagrams by priority
ls.setPriority(6)
ls.setZIndex(1)
layer.setDiagramLayerSettings(ls)
s.scaleBasedVisibility = True
s.minimumScale = 500000 # only show diagrams when zoomed in beyond 1:500,000
r.setDiagramSettings(s)
layer.triggerRepaint()
Breakdown: Hiding colliding diagrams keeps the map readable at the cost of some missing diagrams; raising the priority of the diagram layer above labels decides which wins when both compete. Scale-based visibility makes diagrams appear only when the map is zoomed in far enough for them to fit. Labels on the same layer treat diagrams as obstacles, so place names move off the charts. For print layouts at a fixed scale, check the exported page rather than the canvas, because the layout's map item renders at its own scale.
Legends and captions
Diagrams need decoding. Category entries — each colour with its label — come from the diagram settings with a single call. Sized diagrams also need a size legend with reference values: round numbers spanning the data, such as 10,000, 50,000 and 100,000, rather than automatically chosen awkward values. In print layouts, a legend item limited to the diagram layer keeps the key focused, and a one-sentence caption stating that area is proportional to the total and naming the largest value prevents the most common misreading. Adding diagram legends builds all three.
Charts beside the map
Diagrams are small by necessity. When readers need detail — a time series for the area shown, a ranked bar chart of districts, an elevation profile — a full chart beside the map in the print layout is the better tool.
Matplotlib charts saved as SVG at their exact print size drop into layouts as picture items and stay sharp in PDF exports. For atlases, one SVG per coverage feature and a data-defined picture source expression give each page its own chart. Recent QGIS releases also include a native chart layout item that draws from layer attributes directly; scripts can test for it and fall back to matplotlib on older LTRs. In every case, reading colours and category order from the map's renderer keeps chart and map visually consistent. Adding a chart to a print layout covers each route, and plotting layer data with matplotlib the charts themselves.
Designing a diagram map that works
Beyond the individual settings, a few design habits separate useful diagram maps from cluttered ones.
Keep the base quiet. Diagrams over a choropleth force readers to decode two colour schemes at once. A pale, neutral fill with thin boundaries lets diagram colours carry the message.
Limit categories. Four or five categories per diagram is a practical maximum at map size. Group the rest into "other", and say what "other" contains.
Be consistent across a series. Same colours, same category order, same scale on every map. Store the finished design as a QML style and reuse it.
Check composition sums. A pie claims to show a whole; if the category fields do not add up to the real total, the slices misrepresent it. Compare against a total field and add explicit "other" or "not stated" categories when needed.
Test at print size. Export a page at the final scale and look at it at 100 %. Overlaps, invisible slivers and illegible text show up immediately.
from qgis.core import QgsMapLayer
ok, msg = layer.saveNamedStyle("/data/styles/mode_share_diagrams.qml",
categories=QgsMapLayer.Diagrams | QgsMapLayer.Symbology)
print("style saved:", ok)
Breakdown: Saving only the diagram and symbology categories produces a style that applies cleanly to any layer with the same fields — next year's districts, another city — so a series keeps one design without repeating the configuration in code.
Automating diagram maps
Diagrams are configuration, so they automate well. A script that builds the renderer, computes the shared upper value from the data, applies placement and legend settings, and exports a layout per region or per year produces a consistent map series in minutes. Combined with atlas map series and per-page charts, it turns a dataset into a complete set of report pages. Because all settings are expressions and numbers, they can also be stored in a configuration file and changed without touching code — the same separation of design and data that makes programmatic layer styling maintainable.
Diagrams or symbology?
Not every multi-value question needs a diagram. QGIS symbology can carry two variables at once — colour for one, size for another — and a geometry generator or data-defined symbol can draw surprisingly chart-like marks. Diagrams are the better choice when there are three or more values per feature, when the values are parts of a whole, or when readers should compare categories within each feature. Symbology is better when there are one or two values, when the mark should integrate tightly with the feature's own style, or when the map will be served as vector tiles to web clients that do not render QGIS diagrams. A proportional symbol coloured by a share — circle size for total votes, fill colour for the winning party's margin — often says as much as a pie with far less ink. Proportional symbol maps and geometry generator symbol layers cover those alternatives.
Diagrams outside QGIS Desktop
Diagrams render wherever QGIS's rendering engine runs: in standalone scripts that export images, in print layouts generated headless on a server, and in maps published through QGIS Server as WMS, where GetMap responses include the diagrams and GetLegendGraphic includes their legend entries. They do not travel into formats rendered by other software: a GeoPackage or vector tile set carries the data, not the diagrams, and web map libraries draw their own symbology. If a diagram map must appear on a web page, publish it as WMS or as pre-rendered tiles, or rebuild the charts on the client.
When a diagram map goes wrong
- Nothing appears.
enabledis false, layer settings were never assigned, or the current scale is outside the visibility range. - Some features have no diagram. A category expression returned NULL — wrap fields in
coalesce— or collision handling dropped them. - Big features look enormous. Area scaling is off, so diameter is proportional to value.
- Maps in a series disagree. Each computed its own upper value; share one.
- The legend shows awkward numbers. Size classes were chosen automatically; set round values.
- Labels sit on top of diagrams. Diagram priority is too low or labels are not treating diagrams as obstacles.
A worked example: commuting mode share
Putting the pieces together, a mode-share map for a city's districts takes a few decisions and a short script. The message is composition with magnitude — how people travel, and how many commuters each district has — so the choice is a pie sized by total commuters. Detailed modes are grouped into four categories; colours come from a qualitative palette over a pale base; area scales from zero to the largest district; colliding pies are dropped below 1:150,000; and the legend shows the four categories plus three round reference sizes.
from qgis.core import QgsLinearlyInterpolatedDiagramRenderer
groups = {"active": '"walk" + "cycle"', "public transport": '"bus" + "tram" + "rail"',
"car": '"car_driver" + "car_passenger"', "other": 'coalesce("other", 0)'}
s.categoryAttributes = [f"coalesce({e}, 0)" for e in groups.values()]
s.categoryLabels = list(groups)
s.categoryColors = [QColor(c) for c in ("#15803d", "#2563eb", "#b45309", "#59645f")]
s.scaleByArea = True
total = " + ".join(s.categoryAttributes)
sized = QgsLinearlyInterpolatedDiagramRenderer()
sized.setDiagram(QgsPieDiagram())
sized.setClassificationAttributeExpression(total)
sized.setLowerValue(0)
sized.setUpperValue(max(f["commuters"] or 0 for f in layer.getFeatures()))
sized.setLowerSize(QSizeF(0, 0))
sized.setUpperSize(QSizeF(16, 16))
sized.setDiagramSettings(s)
sized.setAttributeLegend(True)
layer.setDiagramRenderer(sized)
layer.triggerRepaint()
Breakdown: A dictionary of group names to expressions keeps labels and categories in step and makes regrouping a one-line change. The total that sizes each pie is the sum of the same expressions as its slices, so pie area and slice shares always agree. The upper value comes from the data's largest district; for a series of years, replace it with the maximum over all years. Each remaining step — placement, size legend, caption, layout — follows the recipes listed at the top of this guide.
Key takeaways
- Diagrams are configured by four objects: type, settings, renderer and layer settings.
- Choose pies for parts of a whole, bars for comparison, stacked bars for total plus composition, and text for the numbers themselves.
- Categories are expressions — group detail into four or five broad categories and wrap fields in
coalesce. - Scale size from zero, by area for pies, and with one upper value across a series.
- Manage collisions with priorities and scale ranges, and keep the base map quiet.
- Give every diagram map category entries, a size legend with round values and a caption.
- Use full charts in layouts when readers need detail, with colours taken from the map.
Frequently Asked Questions
Are diagrams the same as the heatmap or point cluster renderers? No. Those are symbology renderers that change how features are drawn; diagrams are an extra layer of charts drawn on top of the features' own symbols.
Can diagrams be animated with the temporal controller? Yes. Diagrams follow the layer's temporal filtering, so each frame shows the features of that time step; see animating with the temporal controller.
Do diagrams export to vector PDF and SVG? Yes. They render as vector graphics in layout exports, so they stay sharp at any zoom.
Can I draw diagrams on a point cloud or raster? No. Diagrams are a vector layer feature. Summarise raster or point cloud values into polygons first, for example with zonal statistics.
Is there a limit to how many features can have diagrams? Not technically, but beyond a few hundred on one page the map becomes unreadable. Aggregate to larger units or rely on collision handling and scale limits.
Related
- PyQGIS Cartography & Data Visualization — the section this guide belongs to
- Graduated & Categorized Renderers — choropleths and proportional symbols, the single-value alternatives
- Labeling & Annotations — the placement engine diagrams share with labels
- Automated Map Layout Generation — layouts, legends and pictures for charts
- PyQGIS and the Python Data Stack — matplotlib and pandas behind layout charts
- Add Pie Chart Diagrams to a Layer in PyQGIS
- Add Histogram Bar Diagrams in PyQGIS
- Add Stacked Bar Diagrams in PyQGIS
- Size Diagrams by Attribute in PyQGIS
- Add a Chart to a Print Layout in PyQGIS
- Add Diagram Legends and Text Diagrams in PyQGIS