[{"data":1,"prerenderedAt":1931},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-chart-to-print-layout-pyqgis":3},{"id":4,"title":5,"body":6,"description":1920,"extension":1921,"meta":1922,"navigation":233,"path":1927,"seo":1928,"stem":1929,"__hash__":1930},"docs\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-chart-to-print-layout-pyqgis\u002Findex.md","Add a Chart to a Print Layout in PyQGIS",{"type":7,"value":8,"toc":1906},"minimark",[9,13,17,26,137,142,157,161,164,565,576,580,583,687,700,704,707,811,1072,1086,1090,1093,1213,1227,1231,1234,1368,1389,1393,1396,1474,1775,1783,1787,1801,1805,1833,1837,1840,1844,1850,1856,1862,1868,1872,1902],[10,11,5],"h1",{"id":12},"add-a-chart-to-a-print-layout-in-pyqgis",[14,15,16],"p",{},"Diagrams on the map work for small, simple charts repeated at every feature. Many reports need something else: one proper chart beside the map — a time series for the area shown, a bar chart ranking the districts, a profile along the route. Print layouts can hold charts like any other item, and PyQGIS can produce them as part of the same script that builds the map, so the chart and the map always describe the same data.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002F","Diagrams & Charts on Maps",". It adds a matplotlib chart to a layout as an SVG picture, produces a different chart for every atlas page, introduces the native chart item available on recent QGIS releases, and keeps charts visually consistent with the map.",[14,27,28],{},[29,30,35,39,43,50,59,69,78,82,89,95,102,106,111,116,121,127,131],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 290","img","A layout page with a title, a map on the left, and a chart and legend on the right, the chart coming from a matplotlib SVG or a native chart item","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"A layout with map and chart",[40,41,42],"desc",{},"A print layout page with a map item on the left taking most of the space, a chart item on the right showing a bar chart of the districts visible in the map, a legend under the chart, and a title across the top. The chart is a picture item pointing to an SVG generated by matplotlib, or a native chart item on recent QGIS releases.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","290","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","One page, map and chart together",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"40","44","680","230","4","#fffdf7","#59645f","stroke-width:1.5",[44,70],{"x":71,"y":72,"width":73,"height":74,"rx":75,"fill":76,"stroke":77,"style":68},"60","58","640","24","2","#f0ebdd","#b8b1a0",[51,79,36],{"x":53,"y":80,"style":81,"fill":56,"textAnchor":57},"75","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold",[44,83],{"x":71,"y":84,"width":85,"height":86,"rx":75,"fill":87,"stroke":88,"style":68},"94","400","166","#eef7f4","#0f766e",[51,90,94],{"x":91,"y":92,"style":93,"fill":88,"textAnchor":57},"260","182","text-anchor:middle;font-size:12.0px;font-family:sans-serif;font-weight:bold","map item",[44,96],{"x":97,"y":84,"width":98,"height":99,"rx":75,"fill":100,"stroke":101,"style":68},"476","224","110","#eff3ff","#2563eb",[44,103],{"x":104,"y":105,"width":74,"height":74,"rx":46,"fill":101,"stroke":101,"style":68},"500","170",[44,107],{"x":108,"y":109,"width":74,"height":110,"rx":46,"fill":101,"stroke":101,"style":68},"534","146","48",[44,112],{"x":113,"y":114,"width":74,"height":115,"rx":46,"fill":101,"stroke":101,"style":68},"568","126","68",[44,117],{"x":118,"y":119,"width":74,"height":120,"rx":46,"fill":101,"stroke":101,"style":68},"602","156","38",[51,122,126],{"x":123,"y":124,"style":125,"fill":101,"textAnchor":57},"650","120","text-anchor:middle;font-size:10.5px;font-family:sans-serif","chart",[44,128],{"x":97,"y":129,"width":98,"height":130,"rx":75,"fill":76,"stroke":77,"style":68},"214","46",[51,132,136],{"x":133,"y":134,"style":125,"fill":135,"textAnchor":57},"588","242","#2f3b35","legend",[138,139,141],"h2",{"id":140},"prerequisites","Prerequisites",[143,144,145,149],"ul",{},[146,147,148],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series, with matplotlib (included with the official installers).",[146,150,151,152,156],{},"A print layout, created by hand or in code as in ",[21,153,155],{"href":154},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-map-item-and-set-extent-pyqgis\u002F","adding a map item and setting its extent",".",[138,158,160],{"id":159},"make-the-chart-with-matplotlib","Make the chart with matplotlib",[14,162,163],{},"The chart is produced as an SVG file. Saving with text kept as text and a transparent background makes it sit cleanly on the layout page.",[165,166,171],"pre",{"className":167,"code":168,"language":169,"meta":170,"style":170},"language-python shiki shiki-themes github-dark","import matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\nfrom qgis.core import QgsProject, QgsFeatureRequest\n\ndistricts = QgsProject.instance().mapLayersByName(\"districts\")[0]\nreq = (QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)\n       .setSubsetOfAttributes([\"name\", \"pop_2026\"], districts.fields()))\nrows = sorted(((f[\"name\"], f[\"pop_2026\"] or 0) for f in districts.getFeatures(req)),\n              key=lambda r: r[1])\n\nplt.rcParams.update({\"font.family\": \"sans-serif\", \"font.size\": 7,\n                     \"svg.fonttype\": \"none\", \"axes.spines.top\": False,\n                     \"axes.spines.right\": False})\nfig, ax = plt.subplots(figsize=(80 \u002F 25.4, 70 \u002F 25.4))   # 80 × 70 mm\nax.barh([r[0] for r in rows], [r[1] \u002F 1000 for r in rows], color=\"#2563eb\")\nax.set_xlabel(\"population (thousands)\")\nfig.tight_layout()\nchart_path = \"\u002Fdata\u002Freports\u002Fcharts\u002Fpopulation_by_district.svg\"\nfig.savefig(chart_path, transparent=True)\nplt.close(fig)\n","python","",[172,173,174,187,200,214,228,235,259,270,288,333,352,357,385,408,421,465,515,526,532,543,559],"code",{"__ignoreMap":170},[175,176,179,183],"span",{"class":177,"line":178},"line",1,[175,180,182],{"class":181},"snl16","import",[175,184,186],{"class":185},"s95oV"," matplotlib\n",[175,188,190,193,197],{"class":177,"line":189},2,[175,191,192],{"class":185},"matplotlib.use(",[175,194,196],{"class":195},"sU2Wk","\"Agg\"",[175,198,199],{"class":185},")\n",[175,201,203,205,208,211],{"class":177,"line":202},3,[175,204,182],{"class":181},[175,206,207],{"class":185}," matplotlib.pyplot ",[175,209,210],{"class":181},"as",[175,212,213],{"class":185}," plt\n",[175,215,217,220,223,225],{"class":177,"line":216},4,[175,218,219],{"class":181},"from",[175,221,222],{"class":185}," qgis.core ",[175,224,182],{"class":181},[175,226,227],{"class":185}," QgsProject, QgsFeatureRequest\n",[175,229,231],{"class":177,"line":230},5,[175,232,234],{"emptyLinePlaceholder":233},true,"\n",[175,236,238,241,244,247,250,253,256],{"class":177,"line":237},6,[175,239,240],{"class":185},"districts ",[175,242,243],{"class":181},"=",[175,245,246],{"class":185}," QgsProject.instance().mapLayersByName(",[175,248,249],{"class":195},"\"districts\"",[175,251,252],{"class":185},")[",[175,254,46],{"class":255},"sDLfK",[175,257,258],{"class":185},"]\n",[175,260,262,265,267],{"class":177,"line":261},7,[175,263,264],{"class":185},"req ",[175,266,243],{"class":181},[175,268,269],{"class":185}," (QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)\n",[175,271,273,276,279,282,285],{"class":177,"line":272},8,[175,274,275],{"class":185},"       .setSubsetOfAttributes([",[175,277,278],{"class":195},"\"name\"",[175,280,281],{"class":185},", ",[175,283,284],{"class":195},"\"pop_2026\"",[175,286,287],{"class":185},"], districts.fields()))\n",[175,289,291,294,296,299,302,304,307,309,312,315,318,321,324,327,330],{"class":177,"line":290},9,[175,292,293],{"class":185},"rows ",[175,295,243],{"class":181},[175,297,298],{"class":255}," sorted",[175,300,301],{"class":185},"(((f[",[175,303,278],{"class":195},[175,305,306],{"class":185},"], f[",[175,308,284],{"class":195},[175,310,311],{"class":185},"] ",[175,313,314],{"class":181},"or",[175,316,317],{"class":255}," 0",[175,319,320],{"class":185},") ",[175,322,323],{"class":181},"for",[175,325,326],{"class":185}," f ",[175,328,329],{"class":181},"in",[175,331,332],{"class":185}," districts.getFeatures(req)),\n",[175,334,336,340,343,346,349],{"class":177,"line":335},10,[175,337,339],{"class":338},"s9osk","              key",[175,341,342],{"class":181},"=lambda",[175,344,345],{"class":185}," r: r[",[175,347,348],{"class":255},"1",[175,350,351],{"class":185},"])\n",[175,353,355],{"class":177,"line":354},11,[175,356,234],{"emptyLinePlaceholder":233},[175,358,360,363,366,369,372,374,377,379,382],{"class":177,"line":359},12,[175,361,362],{"class":185},"plt.rcParams.update({",[175,364,365],{"class":195},"\"font.family\"",[175,367,368],{"class":185},": ",[175,370,371],{"class":195},"\"sans-serif\"",[175,373,281],{"class":185},[175,375,376],{"class":195},"\"font.size\"",[175,378,368],{"class":185},[175,380,381],{"class":255},"7",[175,383,384],{"class":185},",\n",[175,386,388,391,393,396,398,401,403,406],{"class":177,"line":387},13,[175,389,390],{"class":195},"                     \"svg.fonttype\"",[175,392,368],{"class":185},[175,394,395],{"class":195},"\"none\"",[175,397,281],{"class":185},[175,399,400],{"class":195},"\"axes.spines.top\"",[175,402,368],{"class":185},[175,404,405],{"class":255},"False",[175,407,384],{"class":185},[175,409,411,414,416,418],{"class":177,"line":410},14,[175,412,413],{"class":195},"                     \"axes.spines.right\"",[175,415,368],{"class":185},[175,417,405],{"class":255},[175,419,420],{"class":185},"})\n",[175,422,424,427,429,432,435,437,440,443,446,449,451,454,456,458,461],{"class":177,"line":423},15,[175,425,426],{"class":185},"fig, ax ",[175,428,243],{"class":181},[175,430,431],{"class":185}," plt.subplots(",[175,433,434],{"class":338},"figsize",[175,436,243],{"class":181},[175,438,439],{"class":185},"(",[175,441,442],{"class":255},"80",[175,444,445],{"class":181}," \u002F",[175,447,448],{"class":255}," 25.4",[175,450,281],{"class":185},[175,452,453],{"class":255},"70",[175,455,445],{"class":181},[175,457,448],{"class":255},[175,459,460],{"class":185},"))   ",[175,462,464],{"class":463},"sjoCn","# 80 × 70 mm\n",[175,466,468,471,473,475,477,480,482,485,487,489,492,495,498,500,502,505,508,510,513],{"class":177,"line":467},16,[175,469,470],{"class":185},"ax.barh([r[",[175,472,46],{"class":255},[175,474,311],{"class":185},[175,476,323],{"class":181},[175,478,479],{"class":185}," r ",[175,481,329],{"class":181},[175,483,484],{"class":185}," rows], [r[",[175,486,348],{"class":255},[175,488,311],{"class":185},[175,490,491],{"class":181},"\u002F",[175,493,494],{"class":255}," 1000",[175,496,497],{"class":181}," for",[175,499,479],{"class":185},[175,501,329],{"class":181},[175,503,504],{"class":185}," rows], ",[175,506,507],{"class":338},"color",[175,509,243],{"class":181},[175,511,512],{"class":195},"\"#2563eb\"",[175,514,199],{"class":185},[175,516,518,521,524],{"class":177,"line":517},17,[175,519,520],{"class":185},"ax.set_xlabel(",[175,522,523],{"class":195},"\"population (thousands)\"",[175,525,199],{"class":185},[175,527,529],{"class":177,"line":528},18,[175,530,531],{"class":185},"fig.tight_layout()\n",[175,533,535,538,540],{"class":177,"line":534},19,[175,536,537],{"class":185},"chart_path ",[175,539,243],{"class":181},[175,541,542],{"class":195}," \"\u002Fdata\u002Freports\u002Fcharts\u002Fpopulation_by_district.svg\"\n",[175,544,546,549,552,554,557],{"class":177,"line":545},20,[175,547,548],{"class":185},"fig.savefig(chart_path, ",[175,550,551],{"class":338},"transparent",[175,553,243],{"class":181},[175,555,556],{"class":255},"True",[175,558,199],{"class":185},[175,560,562],{"class":177,"line":561},21,[175,563,564],{"class":185},"plt.close(fig)\n",[14,566,567,571,572,156],{},[568,569,570],"strong",{},"Breakdown:"," Setting the figure size in inches from the intended size in millimetres means the chart's fonts and line widths come out at their true print size — a 7 pt label stays 7 pt in the PDF. Keeping SVG text as text lets it render with the same fonts as the rest of the layout and stay selectable in the exported PDF. A transparent background lets the layout's own page colour or frame show through. More chart types are covered in ",[21,573,575],{"href":574},"\u002Fspatial-data-processing-automation\u002Fpyqgis-and-the-python-data-stack\u002Fplot-layer-data-with-matplotlib-pyqgis\u002F","plotting layer data with matplotlib",[138,577,579],{"id":578},"add-the-chart-as-a-picture-item","Add the chart as a picture item",[14,581,582],{},"A picture item displays an SVG or raster image at a position and size on the page. SVGs stay sharp at any zoom and in PDF exports.",[165,584,586],{"className":167,"code":585,"language":169,"meta":170,"style":170},"from qgis.core import (QgsLayoutItemPicture, QgsLayoutPoint, QgsLayoutSize,\n                       QgsUnitTypes)\n\nlayout = QgsProject.instance().layoutManager().layoutByName(\"District report\")\nchart = QgsLayoutItemPicture(layout)\nchart.setId(\"population_chart\")\nchart.setPicturePath(chart_path)\nchart.setResizeMode(QgsLayoutItemPicture.Zoom)\nchart.attemptMove(QgsLayoutPoint(205, 25, QgsUnitTypes.LayoutMillimeters))\nchart.attemptResize(QgsLayoutSize(80, 70, QgsUnitTypes.LayoutMillimeters))\nlayout.addLayoutItem(chart)\n",[172,587,588,599,604,608,623,633,643,648,653,669,682],{"__ignoreMap":170},[175,589,590,592,594,596],{"class":177,"line":178},[175,591,219],{"class":181},[175,593,222],{"class":185},[175,595,182],{"class":181},[175,597,598],{"class":185}," (QgsLayoutItemPicture, QgsLayoutPoint, QgsLayoutSize,\n",[175,600,601],{"class":177,"line":189},[175,602,603],{"class":185},"                       QgsUnitTypes)\n",[175,605,606],{"class":177,"line":202},[175,607,234],{"emptyLinePlaceholder":233},[175,609,610,613,615,618,621],{"class":177,"line":216},[175,611,612],{"class":185},"layout ",[175,614,243],{"class":181},[175,616,617],{"class":185}," QgsProject.instance().layoutManager().layoutByName(",[175,619,620],{"class":195},"\"District report\"",[175,622,199],{"class":185},[175,624,625,628,630],{"class":177,"line":230},[175,626,627],{"class":185},"chart ",[175,629,243],{"class":181},[175,631,632],{"class":185}," QgsLayoutItemPicture(layout)\n",[175,634,635,638,641],{"class":177,"line":237},[175,636,637],{"class":185},"chart.setId(",[175,639,640],{"class":195},"\"population_chart\"",[175,642,199],{"class":185},[175,644,645],{"class":177,"line":261},[175,646,647],{"class":185},"chart.setPicturePath(chart_path)\n",[175,649,650],{"class":177,"line":272},[175,651,652],{"class":185},"chart.setResizeMode(QgsLayoutItemPicture.Zoom)\n",[175,654,655,658,661,663,666],{"class":177,"line":290},[175,656,657],{"class":185},"chart.attemptMove(QgsLayoutPoint(",[175,659,660],{"class":255},"205",[175,662,281],{"class":185},[175,664,665],{"class":255},"25",[175,667,668],{"class":185},", QgsUnitTypes.LayoutMillimeters))\n",[175,670,671,674,676,678,680],{"class":177,"line":335},[175,672,673],{"class":185},"chart.attemptResize(QgsLayoutSize(",[175,675,442],{"class":255},[175,677,281],{"class":185},[175,679,453],{"class":255},[175,681,668],{"class":185},[175,683,684],{"class":177,"line":354},[175,685,686],{"class":185},"layout.addLayoutItem(chart)\n",[14,688,689,691,692,695,696,699],{},[568,690,570],{}," Giving the item an id makes it easy to find later with ",[172,693,694],{},"layout.itemById(\"population_chart\")",", for example to swap the picture path in a later run. ",[172,697,698],{},"Zoom"," resize mode scales the SVG to fit the frame while keeping its aspect ratio; because the SVG was created at exactly 80 × 70 mm, it fills the frame without scaling. The layout stores the path, not the image, so regenerating the SVG and re-exporting the layout updates the chart without touching the layout.",[138,701,703],{"id":702},"one-chart-per-atlas-page","One chart per atlas page",[14,705,706],{},"In an atlas, each page shows one feature — one district, one catchment. The chart should change with it. Generating one SVG per feature, named by a key, and pointing the picture item at an expression achieves that.",[14,708,709],{},[29,710,713,716,719,722,737,740,744,750,755,759,763,769,773,778,783,788,792,797,803,807],{"viewBox":711,"role":32,"ariaLabel":712,"xmlns":34},"0 0 760 206","A script generates one chart file per atlas feature, and the picture item's path expression loads the matching file on each page",[36,714,715],{},"Per-page charts in an atlas",[40,717,718],{},"Before export, a script generates one chart file per coverage feature, named by its id, for example charts\u002FD01.svg, charts\u002FD02.svg. The picture item's source is data-defined by an expression that builds the path from the current atlas feature's id. As the atlas moves from page to page, the picture item loads the matching chart.",[44,720],{"x":46,"y":46,"width":47,"height":721,"fill":49},"206",[723,724,725],"defs",{},[726,727,733],"marker",{"id":728,"viewBox":729,"refX":730,"refY":731,"markerWidth":381,"markerHeight":381,"orient":732},"lcAtlasArrow","0 0 10 10","8","5","auto-start-reverse",[734,735],"path",{"d":736,"fill":135},"M0 0 L10 5 L0 10 z",[51,738,739],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"One file per feature, one expression in the layout",[44,741],{"x":74,"y":71,"width":64,"height":742,"rx":730,"fill":87,"stroke":88,"style":743},"130","stroke-width:2",[51,745,749],{"x":746,"y":747,"style":748,"fill":88,"textAnchor":57},"139","92.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","generate",[51,751,754],{"x":746,"y":752,"style":753,"fill":135,"textAnchor":57},"116.78","text-anchor:middle;font-size:10.0px;font-family:monospace","charts\u002FD01.svg",[51,756,758],{"x":746,"y":757,"style":753,"fill":135,"textAnchor":57},"140.78","charts\u002FD02.svg",[51,760,762],{"x":746,"y":761,"style":753,"fill":135,"textAnchor":57},"164.78","…",[177,764],{"x1":765,"y1":766,"x2":767,"y2":766,"stroke":135,"style":768},"254","125","296","stroke-width:1.8;marker-end:url(#lcAtlasArrow)",[44,770],{"x":771,"y":71,"width":772,"height":742,"rx":730,"fill":100,"stroke":101,"style":743},"300","220",[51,774,777],{"x":775,"y":776,"style":81,"fill":101,"textAnchor":57},"410","102.6","picture source",[51,779,782],{"x":775,"y":780,"style":781,"fill":135,"textAnchor":57},"128.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","data-defined",[51,784,787],{"x":775,"y":785,"style":786,"fill":135,"textAnchor":57},"154.6","text-anchor:middle;font-size:9.5px;font-family:monospace","'charts\u002F' || \"code\" || '.svg'",[177,789],{"x1":790,"y1":766,"x2":791,"y2":766,"stroke":135,"style":768},"520","562",[44,793],{"x":794,"y":71,"width":105,"height":742,"rx":730,"fill":795,"stroke":796,"style":743},"566","#e8efe6","#15803d",[51,798,802],{"x":799,"y":800,"style":748,"fill":801,"textAnchor":57},"651","102.78","#166534","atlas page",[51,804,806],{"x":799,"y":805,"style":125,"fill":135,"textAnchor":57},"128.78","loads its chart",[51,808,810],{"x":799,"y":809,"style":125,"fill":67,"textAnchor":57},"154.78","one per feature",[165,812,814],{"className":167,"code":813,"language":169,"meta":170,"style":170},"from qgis.core import QgsLayoutObject, QgsProperty\n\nseries = QgsProject.instance().mapLayersByName(\"district_population_series\")[0]\nby_district = {}\nfor f in series.getFeatures():\n    by_district.setdefault(f[\"code\"], []).append((f[\"year\"], f[\"population\"]))\n\nfor code, points in by_district.items():\n    points.sort()\n    fig, ax = plt.subplots(figsize=(80 \u002F 25.4, 45 \u002F 25.4))\n    ax.plot([p[0] for p in points], [p[1] \u002F 1000 for p in points], color=\"#0f766e\")\n    ax.set_ylabel(\"population (k)\")\n    fig.tight_layout()\n    fig.savefig(f\"\u002Fdata\u002Freports\u002Fcharts\u002F{code}.svg\", transparent=True)\n    plt.close(fig)\n\nchart.dataDefinedProperties().setProperty(\n    QgsLayoutObject.PictureSource,\n    QgsProperty.fromExpression(\"'\u002Fdata\u002Freports\u002Fcharts\u002F' || attribute(@atlas_feature, 'code') || '.svg'\"))\nchart.refreshDataDefinedProperty(QgsLayoutObject.PictureSource)\n",[172,815,816,827,831,849,859,870,892,896,908,913,946,991,1001,1006,1038,1043,1047,1052,1057,1067],{"__ignoreMap":170},[175,817,818,820,822,824],{"class":177,"line":178},[175,819,219],{"class":181},[175,821,222],{"class":185},[175,823,182],{"class":181},[175,825,826],{"class":185}," QgsLayoutObject, QgsProperty\n",[175,828,829],{"class":177,"line":189},[175,830,234],{"emptyLinePlaceholder":233},[175,832,833,836,838,840,843,845,847],{"class":177,"line":202},[175,834,835],{"class":185},"series ",[175,837,243],{"class":181},[175,839,246],{"class":185},[175,841,842],{"class":195},"\"district_population_series\"",[175,844,252],{"class":185},[175,846,46],{"class":255},[175,848,258],{"class":185},[175,850,851,854,856],{"class":177,"line":216},[175,852,853],{"class":185},"by_district ",[175,855,243],{"class":181},[175,857,858],{"class":185}," {}\n",[175,860,861,863,865,867],{"class":177,"line":230},[175,862,323],{"class":181},[175,864,326],{"class":185},[175,866,329],{"class":181},[175,868,869],{"class":185}," series.getFeatures():\n",[175,871,872,875,878,881,884,886,889],{"class":177,"line":237},[175,873,874],{"class":185},"    by_district.setdefault(f[",[175,876,877],{"class":195},"\"code\"",[175,879,880],{"class":185},"], []).append((f[",[175,882,883],{"class":195},"\"year\"",[175,885,306],{"class":185},[175,887,888],{"class":195},"\"population\"",[175,890,891],{"class":185},"]))\n",[175,893,894],{"class":177,"line":261},[175,895,234],{"emptyLinePlaceholder":233},[175,897,898,900,903,905],{"class":177,"line":272},[175,899,323],{"class":181},[175,901,902],{"class":185}," code, points ",[175,904,329],{"class":181},[175,906,907],{"class":185}," by_district.items():\n",[175,909,910],{"class":177,"line":290},[175,911,912],{"class":185},"    points.sort()\n",[175,914,915,918,920,922,924,926,928,930,932,934,936,939,941,943],{"class":177,"line":335},[175,916,917],{"class":185},"    fig, ax ",[175,919,243],{"class":181},[175,921,431],{"class":185},[175,923,434],{"class":338},[175,925,243],{"class":181},[175,927,439],{"class":185},[175,929,442],{"class":255},[175,931,445],{"class":181},[175,933,448],{"class":255},[175,935,281],{"class":185},[175,937,938],{"class":255},"45",[175,940,445],{"class":181},[175,942,448],{"class":255},[175,944,945],{"class":185},"))\n",[175,947,948,951,953,955,957,960,962,965,967,969,971,973,975,977,979,982,984,986,989],{"class":177,"line":354},[175,949,950],{"class":185},"    ax.plot([p[",[175,952,46],{"class":255},[175,954,311],{"class":185},[175,956,323],{"class":181},[175,958,959],{"class":185}," p ",[175,961,329],{"class":181},[175,963,964],{"class":185}," points], [p[",[175,966,348],{"class":255},[175,968,311],{"class":185},[175,970,491],{"class":181},[175,972,494],{"class":255},[175,974,497],{"class":181},[175,976,959],{"class":185},[175,978,329],{"class":181},[175,980,981],{"class":185}," points], ",[175,983,507],{"class":338},[175,985,243],{"class":181},[175,987,988],{"class":195},"\"#0f766e\"",[175,990,199],{"class":185},[175,992,993,996,999],{"class":177,"line":359},[175,994,995],{"class":185},"    ax.set_ylabel(",[175,997,998],{"class":195},"\"population (k)\"",[175,1000,199],{"class":185},[175,1002,1003],{"class":177,"line":387},[175,1004,1005],{"class":185},"    fig.tight_layout()\n",[175,1007,1008,1011,1014,1017,1020,1022,1025,1028,1030,1032,1034,1036],{"class":177,"line":410},[175,1009,1010],{"class":185},"    fig.savefig(",[175,1012,1013],{"class":181},"f",[175,1015,1016],{"class":195},"\"\u002Fdata\u002Freports\u002Fcharts\u002F",[175,1018,1019],{"class":255},"{",[175,1021,172],{"class":185},[175,1023,1024],{"class":255},"}",[175,1026,1027],{"class":195},".svg\"",[175,1029,281],{"class":185},[175,1031,551],{"class":338},[175,1033,243],{"class":181},[175,1035,556],{"class":255},[175,1037,199],{"class":185},[175,1039,1040],{"class":177,"line":423},[175,1041,1042],{"class":185},"    plt.close(fig)\n",[175,1044,1045],{"class":177,"line":467},[175,1046,234],{"emptyLinePlaceholder":233},[175,1048,1049],{"class":177,"line":517},[175,1050,1051],{"class":185},"chart.dataDefinedProperties().setProperty(\n",[175,1053,1054],{"class":177,"line":528},[175,1055,1056],{"class":185},"    QgsLayoutObject.PictureSource,\n",[175,1058,1059,1062,1065],{"class":177,"line":534},[175,1060,1061],{"class":185},"    QgsProperty.fromExpression(",[175,1063,1064],{"class":195},"\"'\u002Fdata\u002Freports\u002Fcharts\u002F' || attribute(@atlas_feature, 'code') || '.svg'\"",[175,1066,945],{"class":185},[175,1068,1069],{"class":177,"line":545},[175,1070,1071],{"class":185},"chart.refreshDataDefinedProperty(QgsLayoutObject.PictureSource)\n",[14,1073,1074,1076,1077,1080,1081,1085],{},[568,1075,570],{}," Generating every chart before export keeps the export itself fast and simple. The data-defined picture source is evaluated for each atlas page, with ",[172,1078,1079],{},"@atlas_feature"," holding the current coverage feature, so the item loads the chart named after that page's district code. Use a stable business key rather than a feature id in file names, so charts and features stay matched even if the layer is rewritten. ",[21,1082,1084],{"href":1083},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fatlas-expressions-and-dynamic-text-pyqgis\u002F","Atlas expressions and dynamic text"," covers the atlas variables available.",[138,1087,1089],{"id":1088},"the-native-chart-item","The native chart item",[14,1091,1092],{},"Recent QGIS releases include a native chart layout item that draws bar and line charts directly from a layer's attributes, without matplotlib, and can filter to the current atlas feature automatically. Where it is available, it avoids managing files altogether.",[165,1094,1096],{"className":167,"code":1095,"language":169,"meta":170,"style":170},"import qgis.core as core\n\nif hasattr(core, \"QgsLayoutItemChart\"):\n    native = core.QgsLayoutItemChart(layout)\n    native.setId(\"native_chart\")\n    native.attemptMove(QgsLayoutPoint(205, 100, QgsUnitTypes.LayoutMillimeters))\n    native.attemptResize(QgsLayoutSize(80, 60, QgsUnitTypes.LayoutMillimeters))\n    layout.addLayoutItem(native)\n    print(\"native chart item available; configure its plot and series in the item properties\")\nelse:\n    print(\"this QGIS version has no native chart item; use matplotlib pictures\")\n",[172,1097,1098,1109,1113,1130,1140,1150,1164,1177,1182,1194,1202],{"__ignoreMap":170},[175,1099,1100,1102,1104,1106],{"class":177,"line":178},[175,1101,182],{"class":181},[175,1103,222],{"class":185},[175,1105,210],{"class":181},[175,1107,1108],{"class":185}," core\n",[175,1110,1111],{"class":177,"line":189},[175,1112,234],{"emptyLinePlaceholder":233},[175,1114,1115,1118,1121,1124,1127],{"class":177,"line":202},[175,1116,1117],{"class":181},"if",[175,1119,1120],{"class":255}," hasattr",[175,1122,1123],{"class":185},"(core, ",[175,1125,1126],{"class":195},"\"QgsLayoutItemChart\"",[175,1128,1129],{"class":185},"):\n",[175,1131,1132,1135,1137],{"class":177,"line":216},[175,1133,1134],{"class":185},"    native ",[175,1136,243],{"class":181},[175,1138,1139],{"class":185}," core.QgsLayoutItemChart(layout)\n",[175,1141,1142,1145,1148],{"class":177,"line":230},[175,1143,1144],{"class":185},"    native.setId(",[175,1146,1147],{"class":195},"\"native_chart\"",[175,1149,199],{"class":185},[175,1151,1152,1155,1157,1159,1162],{"class":177,"line":237},[175,1153,1154],{"class":185},"    native.attemptMove(QgsLayoutPoint(",[175,1156,660],{"class":255},[175,1158,281],{"class":185},[175,1160,1161],{"class":255},"100",[175,1163,668],{"class":185},[175,1165,1166,1169,1171,1173,1175],{"class":177,"line":261},[175,1167,1168],{"class":185},"    native.attemptResize(QgsLayoutSize(",[175,1170,442],{"class":255},[175,1172,281],{"class":185},[175,1174,71],{"class":255},[175,1176,668],{"class":185},[175,1178,1179],{"class":177,"line":272},[175,1180,1181],{"class":185},"    layout.addLayoutItem(native)\n",[175,1183,1184,1187,1189,1192],{"class":177,"line":290},[175,1185,1186],{"class":255},"    print",[175,1188,439],{"class":185},[175,1190,1191],{"class":195},"\"native chart item available; configure its plot and series in the item properties\"",[175,1193,199],{"class":185},[175,1195,1196,1199],{"class":177,"line":335},[175,1197,1198],{"class":181},"else",[175,1200,1201],{"class":185},":\n",[175,1203,1204,1206,1208,1211],{"class":177,"line":354},[175,1205,1186],{"class":255},[175,1207,439],{"class":185},[175,1209,1210],{"class":195},"\"this QGIS version has no native chart item; use matplotlib pictures\"",[175,1212,199],{"class":185},[14,1214,1215,1217,1218,1221,1222,1226],{},[568,1216,570],{}," Testing for the class keeps one script working on both older LTRs and newer releases. The native item's plot types, series and styling are configured through its plot object, whose API is new and still evolving; inspect it with ",[172,1219,1220],{},"help(core.QgsLayoutItemChart)"," on your version, or configure the item once in the Layout Designer, save the layout as a template, and load the template from Python as described in ",[21,1223,1225],{"href":1224},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fload-layout-from-qpt-template-pyqgis\u002F","loading a layout from a template",". For publication-quality control over every detail, matplotlib remains the more flexible tool.",[138,1228,1230],{"id":1229},"chart-only-what-the-map-shows","Chart only what the map shows",[14,1232,1233],{},"A chart beside a zoomed-in map should describe the features in that map, not the whole layer. The map item knows its extent, so the chart script can filter features to it before plotting.",[165,1235,1237],{"className":167,"code":1236,"language":169,"meta":170,"style":170},"from qgis.core import QgsFeatureRequest, QgsCoordinateTransform\n\nmap_item = layout.itemById(\"main_map\")\nextent = map_item.extent()                     # in the map item's CRS\nto_layer = QgsCoordinateTransform(map_item.crs(), districts.crs(), QgsProject.instance())\nlayer_extent = to_layer.transformBoundingBox(extent)\n\nvisible = [(f[\"name\"], f[\"pop_2026\"] or 0)\n           for f in districts.getFeatures(QgsFeatureRequest().setFilterRect(layer_extent))\n           if f.geometry().intersects(layer_extent)]\nprint(len(visible), \"districts in the map frame\")\n",[172,1238,1239,1250,1254,1269,1282,1292,1302,1306,1330,1342,1350],{"__ignoreMap":170},[175,1240,1241,1243,1245,1247],{"class":177,"line":178},[175,1242,219],{"class":181},[175,1244,222],{"class":185},[175,1246,182],{"class":181},[175,1248,1249],{"class":185}," QgsFeatureRequest, QgsCoordinateTransform\n",[175,1251,1252],{"class":177,"line":189},[175,1253,234],{"emptyLinePlaceholder":233},[175,1255,1256,1259,1261,1264,1267],{"class":177,"line":202},[175,1257,1258],{"class":185},"map_item ",[175,1260,243],{"class":181},[175,1262,1263],{"class":185}," layout.itemById(",[175,1265,1266],{"class":195},"\"main_map\"",[175,1268,199],{"class":185},[175,1270,1271,1274,1276,1279],{"class":177,"line":216},[175,1272,1273],{"class":185},"extent ",[175,1275,243],{"class":181},[175,1277,1278],{"class":185}," map_item.extent()                     ",[175,1280,1281],{"class":463},"# in the map item's CRS\n",[175,1283,1284,1287,1289],{"class":177,"line":230},[175,1285,1286],{"class":185},"to_layer ",[175,1288,243],{"class":181},[175,1290,1291],{"class":185}," QgsCoordinateTransform(map_item.crs(), districts.crs(), QgsProject.instance())\n",[175,1293,1294,1297,1299],{"class":177,"line":237},[175,1295,1296],{"class":185},"layer_extent ",[175,1298,243],{"class":181},[175,1300,1301],{"class":185}," to_layer.transformBoundingBox(extent)\n",[175,1303,1304],{"class":177,"line":261},[175,1305,234],{"emptyLinePlaceholder":233},[175,1307,1308,1311,1313,1316,1318,1320,1322,1324,1326,1328],{"class":177,"line":272},[175,1309,1310],{"class":185},"visible ",[175,1312,243],{"class":181},[175,1314,1315],{"class":185}," [(f[",[175,1317,278],{"class":195},[175,1319,306],{"class":185},[175,1321,284],{"class":195},[175,1323,311],{"class":185},[175,1325,314],{"class":181},[175,1327,317],{"class":255},[175,1329,199],{"class":185},[175,1331,1332,1335,1337,1339],{"class":177,"line":290},[175,1333,1334],{"class":181},"           for",[175,1336,326],{"class":185},[175,1338,329],{"class":181},[175,1340,1341],{"class":185}," districts.getFeatures(QgsFeatureRequest().setFilterRect(layer_extent))\n",[175,1343,1344,1347],{"class":177,"line":335},[175,1345,1346],{"class":181},"           if",[175,1348,1349],{"class":185}," f.geometry().intersects(layer_extent)]\n",[175,1351,1352,1355,1357,1360,1363,1366],{"class":177,"line":354},[175,1353,1354],{"class":255},"print",[175,1356,439],{"class":185},[175,1358,1359],{"class":255},"len",[175,1361,1362],{"class":185},"(visible), ",[175,1364,1365],{"class":195},"\"districts in the map frame\"",[175,1367,199],{"class":185},[14,1369,1370,1372,1373,1376,1377,1380,1381,1384,1385,1388],{},[568,1371,570],{}," The map item's extent is a rectangle in the map's CRS; transforming it to the layer's CRS before filtering keeps the comparison correct when the two differ. ",[172,1374,1375],{},"setFilterRect"," uses the spatial index to find candidates quickly, and the exact ",[172,1378,1379],{},"intersects"," test removes features whose bounding box touches the frame but whose shape does not. Feeding ",[172,1382,1383],{},"visible"," to the plotting code in place of ",[172,1386,1387],{},"rows"," gives a chart that always matches the map, and in an atlas the same code can run per page after the atlas has set each page's extent.",[138,1390,1392],{"id":1391},"match-charts-to-the-map","Match charts to the map",[14,1394,1395],{},"A chart that uses different colours, fonts or category order from the map forces readers to translate between them. Take styling from the map rather than choosing it twice.",[14,1397,1398],{},[29,1399,1402,1405,1408,1411,1418,1421,1424,1429,1433,1437,1442,1448,1451,1454,1457,1461,1465,1468,1471],{"viewBox":1400,"role":32,"ariaLabel":1401,"xmlns":34},"0 0 760 196","Chart bar colours read from the map's categorized renderer so both use the same colours in the same order",[36,1403,1404],{},"Colours from the renderer",[40,1406,1407],{},"A categorized renderer on the map defines a colour for each land use. The chart script reads those category colours from the renderer and uses them for the bars, in the same order. Fonts and sizes follow the layout's text styles. The chart and map then share one visual language.",[44,1409],{"x":46,"y":46,"width":47,"height":1410,"fill":49},"196",[723,1412,1413],{},[726,1414,1416],{"id":1415,"viewBox":729,"refX":730,"refY":731,"markerWidth":381,"markerHeight":381,"orient":732},"lcMatchArrow",[734,1417],{"d":736,"fill":135},[51,1419,1420],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"One visual language for map and chart",[44,1422],{"x":74,"y":71,"width":1423,"height":124,"rx":730,"fill":87,"stroke":88,"style":743},"240",[51,1425,1428],{"x":1426,"y":1427,"style":748,"fill":88,"textAnchor":57},"144","97.78","map renderer",[51,1430,1432],{"x":1426,"y":1431,"style":125,"fill":135,"textAnchor":57},"123.78","categories",[51,1434,1436],{"x":1426,"y":1435,"style":125,"fill":67,"textAnchor":57},"149.78","colour per value",[177,1438],{"x1":1439,"y1":124,"x2":1440,"y2":124,"stroke":135,"style":1441},"264","306","stroke-width:1.8;marker-end:url(#lcMatchArrow)",[44,1443],{"x":1444,"y":71,"width":1445,"height":124,"rx":730,"fill":1446,"stroke":1447,"style":743},"310","200","#fdf2e2","#b45309",[51,1449,1450],{"x":775,"y":1427,"style":748,"fill":1447,"textAnchor":57},"chart script",[51,1452,1453],{"x":775,"y":1431,"style":125,"fill":135,"textAnchor":57},"reads symbol",[51,1455,1456],{"x":775,"y":1435,"style":125,"fill":135,"textAnchor":57},"colours",[177,1458],{"x1":1459,"y1":124,"x2":1460,"y2":124,"stroke":135,"style":1441},"510","552",[44,1462],{"x":1463,"y":71,"width":1464,"height":124,"rx":730,"fill":100,"stroke":101,"style":743},"556","180",[51,1466,126],{"x":1467,"y":1427,"style":748,"fill":101,"textAnchor":57},"646",[51,1469,1470],{"x":1467,"y":1431,"style":125,"fill":135,"textAnchor":57},"same colours",[51,1472,1473],{"x":1467,"y":1435,"style":125,"fill":135,"textAnchor":57},"same order",[165,1475,1477],{"className":167,"code":1476,"language":169,"meta":170,"style":170},"landuse = QgsProject.instance().mapLayersByName(\"landuse\")[0]\nrenderer = landuse.renderer()          # a QgsCategorizedSymbolRenderer\ncolours = {str(cat.value()): cat.symbol().color().name() for cat in renderer.categories()}\nlabels = {str(cat.value()): cat.label() for cat in renderer.categories()}\n\nareas = {}\nfor f in landuse.getFeatures():\n    areas[str(f[\"class\"])] = areas.get(str(f[\"class\"]), 0) + f.geometry().area() \u002F 1e4\norder = [str(cat.value()) for cat in renderer.categories() if str(cat.value()) in areas]\n\nfig, ax = plt.subplots(figsize=(80 \u002F 25.4, 60 \u002F 25.4))\nax.barh([labels[k] for k in order], [areas[k] for k in order],\n        color=[colours[k] for k in order])\nax.set_xlabel(\"area (ha)\")\nfig.tight_layout()\nfig.savefig(\"\u002Fdata\u002Freports\u002Fcharts\u002Flanduse_area.svg\", transparent=True)\nplt.close(fig)\n",[172,1478,1479,1497,1510,1536,1558,1562,1571,1582,1627,1663,1667,1697,1721,1740,1749,1753,1771],{"__ignoreMap":170},[175,1480,1481,1484,1486,1488,1491,1493,1495],{"class":177,"line":178},[175,1482,1483],{"class":185},"landuse ",[175,1485,243],{"class":181},[175,1487,246],{"class":185},[175,1489,1490],{"class":195},"\"landuse\"",[175,1492,252],{"class":185},[175,1494,46],{"class":255},[175,1496,258],{"class":185},[175,1498,1499,1502,1504,1507],{"class":177,"line":189},[175,1500,1501],{"class":185},"renderer ",[175,1503,243],{"class":181},[175,1505,1506],{"class":185}," landuse.renderer()          ",[175,1508,1509],{"class":463},"# a QgsCategorizedSymbolRenderer\n",[175,1511,1512,1515,1517,1520,1523,1526,1528,1531,1533],{"class":177,"line":202},[175,1513,1514],{"class":185},"colours ",[175,1516,243],{"class":181},[175,1518,1519],{"class":185}," {",[175,1521,1522],{"class":255},"str",[175,1524,1525],{"class":185},"(cat.value()): cat.symbol().color().name() ",[175,1527,323],{"class":181},[175,1529,1530],{"class":185}," cat ",[175,1532,329],{"class":181},[175,1534,1535],{"class":185}," renderer.categories()}\n",[175,1537,1538,1541,1543,1545,1547,1550,1552,1554,1556],{"class":177,"line":216},[175,1539,1540],{"class":185},"labels ",[175,1542,243],{"class":181},[175,1544,1519],{"class":185},[175,1546,1522],{"class":255},[175,1548,1549],{"class":185},"(cat.value()): cat.label() ",[175,1551,323],{"class":181},[175,1553,1530],{"class":185},[175,1555,329],{"class":181},[175,1557,1535],{"class":185},[175,1559,1560],{"class":177,"line":230},[175,1561,234],{"emptyLinePlaceholder":233},[175,1563,1564,1567,1569],{"class":177,"line":237},[175,1565,1566],{"class":185},"areas ",[175,1568,243],{"class":181},[175,1570,858],{"class":185},[175,1572,1573,1575,1577,1579],{"class":177,"line":261},[175,1574,323],{"class":181},[175,1576,326],{"class":185},[175,1578,329],{"class":181},[175,1580,1581],{"class":185}," landuse.getFeatures():\n",[175,1583,1584,1587,1589,1592,1595,1598,1600,1603,1605,1607,1609,1612,1614,1616,1619,1622,1624],{"class":177,"line":272},[175,1585,1586],{"class":185},"    areas[",[175,1588,1522],{"class":255},[175,1590,1591],{"class":185},"(f[",[175,1593,1594],{"class":195},"\"class\"",[175,1596,1597],{"class":185},"])] ",[175,1599,243],{"class":181},[175,1601,1602],{"class":185}," areas.get(",[175,1604,1522],{"class":255},[175,1606,1591],{"class":185},[175,1608,1594],{"class":195},[175,1610,1611],{"class":185},"]), ",[175,1613,46],{"class":255},[175,1615,320],{"class":185},[175,1617,1618],{"class":181},"+",[175,1620,1621],{"class":185}," f.geometry().area() ",[175,1623,491],{"class":181},[175,1625,1626],{"class":255}," 1e4\n",[175,1628,1629,1632,1634,1637,1639,1642,1644,1646,1648,1651,1653,1656,1658,1660],{"class":177,"line":290},[175,1630,1631],{"class":185},"order ",[175,1633,243],{"class":181},[175,1635,1636],{"class":185}," [",[175,1638,1522],{"class":255},[175,1640,1641],{"class":185},"(cat.value()) ",[175,1643,323],{"class":181},[175,1645,1530],{"class":185},[175,1647,329],{"class":181},[175,1649,1650],{"class":185}," renderer.categories() ",[175,1652,1117],{"class":181},[175,1654,1655],{"class":255}," str",[175,1657,1641],{"class":185},[175,1659,329],{"class":181},[175,1661,1662],{"class":185}," areas]\n",[175,1664,1665],{"class":177,"line":335},[175,1666,234],{"emptyLinePlaceholder":233},[175,1668,1669,1671,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691,1693,1695],{"class":177,"line":354},[175,1670,426],{"class":185},[175,1672,243],{"class":181},[175,1674,431],{"class":185},[175,1676,434],{"class":338},[175,1678,243],{"class":181},[175,1680,439],{"class":185},[175,1682,442],{"class":255},[175,1684,445],{"class":181},[175,1686,448],{"class":255},[175,1688,281],{"class":185},[175,1690,71],{"class":255},[175,1692,445],{"class":181},[175,1694,448],{"class":255},[175,1696,945],{"class":185},[175,1698,1699,1702,1704,1707,1709,1712,1714,1716,1718],{"class":177,"line":359},[175,1700,1701],{"class":185},"ax.barh([labels[k] ",[175,1703,323],{"class":181},[175,1705,1706],{"class":185}," k ",[175,1708,329],{"class":181},[175,1710,1711],{"class":185}," order], [areas[k] ",[175,1713,323],{"class":181},[175,1715,1706],{"class":185},[175,1717,329],{"class":181},[175,1719,1720],{"class":185}," order],\n",[175,1722,1723,1726,1728,1731,1733,1735,1737],{"class":177,"line":387},[175,1724,1725],{"class":338},"        color",[175,1727,243],{"class":181},[175,1729,1730],{"class":185},"[colours[k] ",[175,1732,323],{"class":181},[175,1734,1706],{"class":185},[175,1736,329],{"class":181},[175,1738,1739],{"class":185}," order])\n",[175,1741,1742,1744,1747],{"class":177,"line":410},[175,1743,520],{"class":185},[175,1745,1746],{"class":195},"\"area (ha)\"",[175,1748,199],{"class":185},[175,1750,1751],{"class":177,"line":423},[175,1752,531],{"class":185},[175,1754,1755,1758,1761,1763,1765,1767,1769],{"class":177,"line":467},[175,1756,1757],{"class":185},"fig.savefig(",[175,1759,1760],{"class":195},"\"\u002Fdata\u002Freports\u002Fcharts\u002Flanduse_area.svg\"",[175,1762,281],{"class":185},[175,1764,551],{"class":338},[175,1766,243],{"class":181},[175,1768,556],{"class":255},[175,1770,199],{"class":185},[175,1772,1773],{"class":177,"line":517},[175,1774,564],{"class":185},[14,1776,1777,1779,1780,156],{},[568,1778,570],{}," Reading colours and labels from the renderer's categories guarantees the chart and the map agree, and following the renderer's category order means the legend, the map and the chart list classes the same way. If the map's style changes, rerunning the script updates the chart to match. The same approach works for graduated renderers through ",[172,1781,1782],{},"renderer.ranges()",[138,1784,1786],{"id":1785},"qgis-version-compatibility","QGIS version compatibility",[14,1788,1789,1790,1793,1794,1797,1798,156],{},"Picture items and data-defined picture sources work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. The native chart layout item is only present on newer releases — test for it as shown. On QGIS 4, ",[172,1791,1792],{},"QgsUnitTypes.LayoutMillimeters"," is ",[172,1795,1796],{},"Qgis.LayoutUnit.Millimeters"," and the picture resize mode is ",[172,1799,1800],{},"QgsLayoutItemPicture.ResizeMode.Zoom",[138,1802,1804],{"id":1803},"troubleshooting","Troubleshooting",[143,1806,1807,1813,1819,1827],{},[146,1808,1809,1812],{},[568,1810,1811],{},"The chart is blurry in the PDF."," It was saved as PNG at low DPI; save SVG, or PNG at 300 DPI.",[146,1814,1815,1818],{},[568,1816,1817],{},"Fonts in the chart look different."," The font named in matplotlib is not installed where QGIS renders the layout; use a font available on that machine.",[146,1820,1821,1824,1825,156],{},[568,1822,1823],{},"Every atlas page shows the same chart."," The picture path is fixed rather than data-defined, or the expression does not reference ",[172,1826,1079],{},[146,1828,1829,1832],{},[568,1830,1831],{},"Charts lag behind the data."," They are files; regenerate them before every export.",[138,1834,1836],{"id":1835},"conclusion","Conclusion",[14,1838,1839],{},"Draw charts with matplotlib at their true print size and save as SVG with text kept as text, place them as picture items, generate one file per feature and use a data-defined source for atlases, use the native chart item where your QGIS version has it, and take colours and order from the map's renderer so chart and map read as one.",[138,1841,1843],{"id":1842},"frequently-asked-questions","Frequently Asked Questions",[14,1845,1846,1849],{},[568,1847,1848],{},"Can the chart be interactive in a PDF?","\nNo. PDFs are static; for interactive charts, publish HTML instead.",[14,1851,1852,1855],{},[568,1853,1854],{},"Can a layout HTML frame show a chart?","\nYes — an HTML item can render a JavaScript chart library, though fonts and print fidelity are harder to control than with SVG.",[14,1857,1858,1861],{},[568,1859,1860],{},"How do I place the chart relative to the map?","\nUse the map item's position and size to compute the chart's, so they stay aligned when the map moves.",[14,1863,1864,1867],{},[568,1865,1866],{},"Can charts use data from the visible map extent only?","\nYes: filter features with the map item's extent before plotting, or use the native chart's filtering where available.",[138,1869,1871],{"id":1870},"related","Related",[143,1873,1874,1879,1884,1890,1896],{},[146,1875,1876,1878],{},[21,1877,24],{"href":23}," — the guide this recipe belongs to",[146,1880,1881],{},[21,1882,1883],{"href":574},"Plot Layer Data with Matplotlib in PyQGIS",[146,1885,1886],{},[21,1887,1889],{"href":1888},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-picture-and-logo-to-layout-pyqgis\u002F","Add Pictures and Logos to a Print Layout in PyQGIS",[146,1891,1892],{},[21,1893,1895],{"href":1894},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis\u002F","Generate an Atlas PDF in PyQGIS",[146,1897,1898],{},[21,1899,1901],{"href":1900},"\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-diagram-legend-and-text-diagrams-pyqgis\u002F","Add Diagram Legends and Text Diagrams in PyQGIS",[1903,1904,1905],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":170,"searchDepth":189,"depth":189,"links":1907},[1908,1909,1910,1911,1912,1913,1914,1915,1916,1917,1918,1919],{"id":140,"depth":189,"text":141},{"id":159,"depth":189,"text":160},{"id":578,"depth":189,"text":579},{"id":702,"depth":189,"text":703},{"id":1088,"depth":189,"text":1089},{"id":1229,"depth":189,"text":1230},{"id":1391,"depth":189,"text":1392},{"id":1785,"depth":189,"text":1786},{"id":1803,"depth":189,"text":1804},{"id":1835,"depth":189,"text":1836},{"id":1842,"depth":189,"text":1843},{"id":1870,"depth":189,"text":1871},"Put charts next to maps in QGIS print layouts — matplotlib SVGs as picture items, one chart per atlas page through a data-defined picture path, QGIS's native chart item on recent releases, and sizing, fonts and colours that match the map.","md",{"slug":1923,"type":1924,"breadcrumb":1925,"datePublished":1926,"dateModified":1926},"add-chart-to-print-layout-pyqgis","article","Add a Chart to a Print Layout","2026-10-02","\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-chart-to-print-layout-pyqgis",{"title":5,"description":1920},"pyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-chart-to-print-layout-pyqgis\u002Findex","Kbx16R4QbpjFqg2is5KwLnxoeHhlCc-pndQZs2Q_nGI",1790966252897]