[{"data":1,"prerenderedAt":1488},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fsize-diagrams-by-attribute-pyqgis":3},{"id":4,"title":5,"body":6,"description":1477,"extension":1478,"meta":1479,"navigation":226,"path":1484,"seo":1485,"stem":1486,"__hash__":1487},"docs\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fsize-diagrams-by-attribute-pyqgis\u002Findex.md","Size Diagrams by Attribute in PyQGIS",{"type":7,"value":8,"toc":1462},"minimark",[9,13,17,26,123,128,153,157,160,544,555,559,562,624,627,788,793,797,800,901,906,910,913,975,1132,1137,1141,1144,1235,1248,1252,1255,1323,1333,1337,1340,1344,1358,1362,1391,1395,1398,1402,1408,1414,1420,1426,1430,1458],[10,11,5],"h1",{"id":12},"size-diagrams-by-attribute-in-pyqgis",[14,15,16],"p",{},"A diagram that only shows composition treats every feature as equally important. Most maps need magnitude too: a pie of party votes is more informative when large constituencies get large pies, a bar of energy sources more honest when a city's bar dwarfs a village's. Size is a powerful visual variable and an easy one to misuse — the same numbers can look like a small difference or a dramatic one depending on how size is computed.",[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 configures the linearly interpolated diagram renderer, explains the choice between scaling by area and by diameter, handles outliers, keeps one scale across several maps, and shows when a data-defined size expression is the better tool.",[14,27,28],{},[29,30,35,39,43,50,59,69,77,82,88,92,98,101,106,110,112,114,119],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 280","img","Circles for values 100 and 400 scaled by diameter, exaggerating the difference sixteen-fold, and by area, matching the four-to-one ratio","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Area scaling versus diameter scaling",[40,41,42],"desc",{},"Two values, 100 and 400. Scaled by diameter, the second circle has four times the diameter and sixteen times the area, so the difference looks far larger than four to one. Scaled by area, the second circle has twice the diameter and four times the area, matching the ratio of the values.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","280","#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","Four times the value should look four times as big",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","48","340","196","8","#fffdf7","#59645f","stroke-width:1.5",[70,71],"circle",{"cx":72,"cy":73,"r":74,"fill":75,"stroke":75,"style":76},"80","170","14","#b91c1c","stroke-width:1",[70,78],{"cx":79,"cy":80,"r":81,"fill":75,"stroke":75,"style":76},"230","146","56",[51,83,87],{"x":72,"y":84,"style":85,"fill":86,"textAnchor":57},"214","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#2f3b35","100",[51,89,91],{"x":79,"y":90,"style":85,"fill":86,"textAnchor":57},"222","400",[51,93,97],{"x":94,"y":95,"style":96,"fill":75,"textAnchor":57},"194","76","text-anchor:middle;font-size:11.0px;font-family:sans-serif","by diameter: looks 16×",[44,99],{"x":100,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"396",[70,102],{"cx":103,"cy":104,"r":61,"fill":105,"stroke":105,"style":76},"470","160","#15803d",[70,107],{"cx":108,"cy":109,"r":62,"fill":105,"stroke":105,"style":76},"620","150",[51,111,87],{"x":103,"y":84,"style":85,"fill":86,"textAnchor":57},[51,113,91],{"x":108,"y":90,"style":85,"fill":86,"textAnchor":57},[51,115,118],{"x":116,"y":95,"style":96,"fill":117,"textAnchor":57},"566","#166534","by area: looks 4×",[51,120,122],{"x":53,"y":121,"style":85,"fill":67,"textAnchor":57},"268","set scaleByArea = True for pies and other area-based diagrams",[124,125,127],"h2",{"id":126},"prerequisites","Prerequisites",[129,130,131,135],"ul",{},[132,133,134],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[132,136,137,138,142,143,147,148,152],{},"A layer with a diagram already configured — a ",[21,139,141],{"href":140},"\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-pie-chart-diagrams-pyqgis\u002F","pie",", ",[21,144,146],{"href":145},"\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-histogram-bar-diagrams-pyqgis\u002F","bar"," or ",[21,149,151],{"href":150},"\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-stacked-bar-diagrams-pyqgis\u002F","stacked bar"," — and a numeric field or expression to size by.",[124,154,156],{"id":155},"configure-the-interpolated-renderer","Configure the interpolated renderer",[14,158,159],{},"The linearly interpolated renderer maps a value range to a size range. Four numbers define it: lower and upper value, lower and upper size.",[161,162,167],"pre",{"className":163,"code":164,"language":165,"meta":166,"style":166},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsProject, QgsPieDiagram, QgsDiagramSettings,\n                       QgsLinearlyInterpolatedDiagramRenderer, QgsDiagramLayerSettings)\nfrom qgis.PyQt.QtGui import QColor\nfrom qgis.PyQt.QtCore import QSizeF\n\nconstituencies = QgsProject.instance().mapLayersByName(\"constituencies\")[0]\n\nsettings = QgsDiagramSettings()\nsettings.categoryAttributes = ['\"votes_a\"', '\"votes_b\"', '\"votes_c\"', '\"votes_other\"']\nsettings.categoryColors = [QColor(c) for c in (\"#2563eb\", \"#b91c1c\", \"#15803d\", \"#59645f\")]\nsettings.categoryLabels = [\"A\", \"B\", \"C\", \"other\"]\nsettings.scaleByArea = True\nsettings.penColor = QColor(\"#ffffff\")\nsettings.enabled = True\n\nrenderer = QgsLinearlyInterpolatedDiagramRenderer()\nrenderer.setDiagram(QgsPieDiagram())\nrenderer.setClassificationAttributeExpression('\"votes_total\"')\nrenderer.setLowerValue(0)\nrenderer.setUpperValue(max(f[\"votes_total\"] or 0 for f in constituencies.getFeatures()))\nrenderer.setLowerSize(QSizeF(0, 0))\nrenderer.setUpperSize(QSizeF(18, 18))\nrenderer.setDiagramSettings(settings)\nconstituencies.setDiagramRenderer(renderer)\nconstituencies.setDiagramLayerSettings(QgsDiagramLayerSettings())\nconstituencies.triggerRepaint()\n","python","",[168,169,170,189,195,208,221,228,253,258,269,300,344,374,385,402,412,417,428,434,445,455,490,505,520,526,532,538],"code",{"__ignoreMap":166},[171,172,175,179,183,186],"span",{"class":173,"line":174},"line",1,[171,176,178],{"class":177},"snl16","from",[171,180,182],{"class":181},"s95oV"," qgis.core ",[171,184,185],{"class":177},"import",[171,187,188],{"class":181}," (QgsProject, QgsPieDiagram, QgsDiagramSettings,\n",[171,190,192],{"class":173,"line":191},2,[171,193,194],{"class":181},"                       QgsLinearlyInterpolatedDiagramRenderer, QgsDiagramLayerSettings)\n",[171,196,198,200,203,205],{"class":173,"line":197},3,[171,199,178],{"class":177},[171,201,202],{"class":181}," qgis.PyQt.QtGui ",[171,204,185],{"class":177},[171,206,207],{"class":181}," QColor\n",[171,209,211,213,216,218],{"class":173,"line":210},4,[171,212,178],{"class":177},[171,214,215],{"class":181}," qgis.PyQt.QtCore ",[171,217,185],{"class":177},[171,219,220],{"class":181}," QSizeF\n",[171,222,224],{"class":173,"line":223},5,[171,225,227],{"emptyLinePlaceholder":226},true,"\n",[171,229,231,234,237,240,244,247,250],{"class":173,"line":230},6,[171,232,233],{"class":181},"constituencies ",[171,235,236],{"class":177},"=",[171,238,239],{"class":181}," QgsProject.instance().mapLayersByName(",[171,241,243],{"class":242},"sU2Wk","\"constituencies\"",[171,245,246],{"class":181},")[",[171,248,46],{"class":249},"sDLfK",[171,251,252],{"class":181},"]\n",[171,254,256],{"class":173,"line":255},7,[171,257,227],{"emptyLinePlaceholder":226},[171,259,261,264,266],{"class":173,"line":260},8,[171,262,263],{"class":181},"settings ",[171,265,236],{"class":177},[171,267,268],{"class":181}," QgsDiagramSettings()\n",[171,270,272,275,277,280,283,285,288,290,293,295,298],{"class":173,"line":271},9,[171,273,274],{"class":181},"settings.categoryAttributes ",[171,276,236],{"class":177},[171,278,279],{"class":181}," [",[171,281,282],{"class":242},"'\"votes_a\"'",[171,284,142],{"class":181},[171,286,287],{"class":242},"'\"votes_b\"'",[171,289,142],{"class":181},[171,291,292],{"class":242},"'\"votes_c\"'",[171,294,142],{"class":181},[171,296,297],{"class":242},"'\"votes_other\"'",[171,299,252],{"class":181},[171,301,303,306,308,311,314,317,320,323,326,328,331,333,336,338,341],{"class":173,"line":302},10,[171,304,305],{"class":181},"settings.categoryColors ",[171,307,236],{"class":177},[171,309,310],{"class":181}," [QColor(c) ",[171,312,313],{"class":177},"for",[171,315,316],{"class":181}," c ",[171,318,319],{"class":177},"in",[171,321,322],{"class":181}," (",[171,324,325],{"class":242},"\"#2563eb\"",[171,327,142],{"class":181},[171,329,330],{"class":242},"\"#b91c1c\"",[171,332,142],{"class":181},[171,334,335],{"class":242},"\"#15803d\"",[171,337,142],{"class":181},[171,339,340],{"class":242},"\"#59645f\"",[171,342,343],{"class":181},")]\n",[171,345,347,350,352,354,357,359,362,364,367,369,372],{"class":173,"line":346},11,[171,348,349],{"class":181},"settings.categoryLabels ",[171,351,236],{"class":177},[171,353,279],{"class":181},[171,355,356],{"class":242},"\"A\"",[171,358,142],{"class":181},[171,360,361],{"class":242},"\"B\"",[171,363,142],{"class":181},[171,365,366],{"class":242},"\"C\"",[171,368,142],{"class":181},[171,370,371],{"class":242},"\"other\"",[171,373,252],{"class":181},[171,375,377,380,382],{"class":173,"line":376},12,[171,378,379],{"class":181},"settings.scaleByArea ",[171,381,236],{"class":177},[171,383,384],{"class":249}," True\n",[171,386,388,391,393,396,399],{"class":173,"line":387},13,[171,389,390],{"class":181},"settings.penColor ",[171,392,236],{"class":177},[171,394,395],{"class":181}," QColor(",[171,397,398],{"class":242},"\"#ffffff\"",[171,400,401],{"class":181},")\n",[171,403,405,408,410],{"class":173,"line":404},14,[171,406,407],{"class":181},"settings.enabled ",[171,409,236],{"class":177},[171,411,384],{"class":249},[171,413,415],{"class":173,"line":414},15,[171,416,227],{"emptyLinePlaceholder":226},[171,418,420,423,425],{"class":173,"line":419},16,[171,421,422],{"class":181},"renderer ",[171,424,236],{"class":177},[171,426,427],{"class":181}," QgsLinearlyInterpolatedDiagramRenderer()\n",[171,429,431],{"class":173,"line":430},17,[171,432,433],{"class":181},"renderer.setDiagram(QgsPieDiagram())\n",[171,435,437,440,443],{"class":173,"line":436},18,[171,438,439],{"class":181},"renderer.setClassificationAttributeExpression(",[171,441,442],{"class":242},"'\"votes_total\"'",[171,444,401],{"class":181},[171,446,448,451,453],{"class":173,"line":447},19,[171,449,450],{"class":181},"renderer.setLowerValue(",[171,452,46],{"class":249},[171,454,401],{"class":181},[171,456,458,461,464,467,470,473,476,479,482,485,487],{"class":173,"line":457},20,[171,459,460],{"class":181},"renderer.setUpperValue(",[171,462,463],{"class":249},"max",[171,465,466],{"class":181},"(f[",[171,468,469],{"class":242},"\"votes_total\"",[171,471,472],{"class":181},"] ",[171,474,475],{"class":177},"or",[171,477,478],{"class":249}," 0",[171,480,481],{"class":177}," for",[171,483,484],{"class":181}," f ",[171,486,319],{"class":177},[171,488,489],{"class":181}," constituencies.getFeatures()))\n",[171,491,493,496,498,500,502],{"class":173,"line":492},21,[171,494,495],{"class":181},"renderer.setLowerSize(QSizeF(",[171,497,46],{"class":249},[171,499,142],{"class":181},[171,501,46],{"class":249},[171,503,504],{"class":181},"))\n",[171,506,508,511,514,516,518],{"class":173,"line":507},22,[171,509,510],{"class":181},"renderer.setUpperSize(QSizeF(",[171,512,513],{"class":249},"18",[171,515,142],{"class":181},[171,517,513],{"class":249},[171,519,504],{"class":181},[171,521,523],{"class":173,"line":522},23,[171,524,525],{"class":181},"renderer.setDiagramSettings(settings)\n",[171,527,529],{"class":173,"line":528},24,[171,530,531],{"class":181},"constituencies.setDiagramRenderer(renderer)\n",[171,533,535],{"class":173,"line":534},25,[171,536,537],{"class":181},"constituencies.setDiagramLayerSettings(QgsDiagramLayerSettings())\n",[171,539,541],{"class":173,"line":540},26,[171,542,543],{"class":181},"constituencies.triggerRepaint()\n",[14,545,546,550,551,554],{},[547,548,549],"strong",{},"Breakdown:"," The classification can be a field or any expression — a sum of parts, a value converted to thousands. Starting both the value and the size range at zero keeps sizes proportional: a constituency with half the votes gets half the area. With ",[168,552,553],{},"scaleByArea"," set, QGIS interpolates the diagram's area rather than its diameter, which is what readers perceive for circles. Sizes are in millimetres by default and stay constant as the map zooms.",[124,556,558],{"id":557},"choose-the-ranges","Choose the ranges",[14,560,561],{},"The upper value and upper size together set the scale; the lower values decide whether small features remain visible.",[14,563,564],{},[29,565,568,571,574,577,580,584,587,590,593,597,604,611,615,619],{"viewBox":566,"role":32,"ariaLabel":567,"xmlns":34},"0 0 760 250","A linear mapping from value range to size range, with the effects of a non-zero minimum size and of an upper value below the true maximum",[36,569,570],{},"Value range to size range",[40,572,573],{},"A line from lower value zero at size zero to upper value at the maximum size. Features with values in between get proportional sizes. Raising the lower size above zero makes small features visible but breaks proportionality near the bottom of the range. Setting the upper value below the true maximum makes the largest features overflow the intended maximum size.",[44,575],{"x":46,"y":46,"width":47,"height":576,"fill":49},"250",[51,578,579],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Zero to zero keeps sizes honest",[173,581],{"x1":72,"y1":582,"x2":583,"y2":582,"stroke":67,"style":68},"200","680",[173,585],{"x1":72,"y1":582,"x2":72,"y2":586,"stroke":67,"style":68},"60",[173,588],{"x1":72,"y1":582,"x2":108,"y2":72,"stroke":105,"style":589},"stroke-width:3",[70,591],{"cx":108,"cy":72,"r":592,"fill":105,"stroke":105,"style":76},"6",[173,594],{"x1":72,"y1":104,"x2":108,"y2":72,"stroke":595,"style":596},"#b45309","stroke-width:2;stroke-dasharray:6 4",[51,598,603],{"x":599,"y":600,"style":601,"fill":117,"textAnchor":602},"608","66","text-anchor:end;font-size:10.0px;font-family:sans-serif","end","upper value → upper size",[51,605,610],{"x":606,"y":607,"style":608,"fill":595,"textAnchor":609},"92","152","text-anchor:start;font-size:10.0px;font-family:sans-serif","start","lower size > 0: small ones visible,",[51,612,614],{"x":606,"y":613,"style":608,"fill":595,"textAnchor":609},"168","but no longer proportional",[51,616,618],{"x":53,"y":617,"style":85,"fill":67,"textAnchor":57},"226","value",[51,620,623],{"x":621,"y":600,"style":622,"fill":67,"textAnchor":602},"70","text-anchor:end;font-size:10.5px;font-family:sans-serif","size",[14,625,626],{},"A practical approach: set the upper size so the largest diagram fits comfortably between its neighbours at the intended scale — often 15–25 mm on a printed map — and the upper value to the data's maximum. If the smallest features become invisible dots, that is information: they really are much smaller. If small features must remain readable as pies, a modest lower size of 2–3 mm is a reasonable compromise, but note it in the legend, because sizes near the bottom are no longer proportional.",[161,628,630],{"className":163,"code":629,"language":165,"meta":166,"style":166},"values = sorted(f[\"votes_total\"] or 0 for f in constituencies.getFeatures())\nratio = values[-1] \u002F max(values[0], 1)\nprint(f\"min {values[0]:,}, median {values[len(values) \u002F\u002F 2]:,}, max {values[-1]:,}, ratio {ratio:.0f}:1\")\n",[168,631,632,661,697],{"__ignoreMap":166},[171,633,634,637,639,642,644,646,648,650,652,654,656,658],{"class":173,"line":174},[171,635,636],{"class":181},"values ",[171,638,236],{"class":177},[171,640,641],{"class":249}," sorted",[171,643,466],{"class":181},[171,645,469],{"class":242},[171,647,472],{"class":181},[171,649,475],{"class":177},[171,651,478],{"class":249},[171,653,481],{"class":177},[171,655,484],{"class":181},[171,657,319],{"class":177},[171,659,660],{"class":181}," constituencies.getFeatures())\n",[171,662,663,666,668,671,674,677,679,682,685,688,690,693,695],{"class":173,"line":191},[171,664,665],{"class":181},"ratio ",[171,667,236],{"class":177},[171,669,670],{"class":181}," values[",[171,672,673],{"class":177},"-",[171,675,676],{"class":249},"1",[171,678,472],{"class":181},[171,680,681],{"class":177},"\u002F",[171,683,684],{"class":249}," max",[171,686,687],{"class":181},"(values[",[171,689,46],{"class":249},[171,691,692],{"class":181},"], ",[171,694,676],{"class":249},[171,696,401],{"class":181},[171,698,699,702,705,708,711,714,717,719,722,725,728,731,733,735,738,741,744,747,749,751,753,756,758,760,762,764,766,768,770,773,775,778,781,783,786],{"class":173,"line":197},[171,700,701],{"class":249},"print",[171,703,704],{"class":181},"(",[171,706,707],{"class":177},"f",[171,709,710],{"class":242},"\"min ",[171,712,713],{"class":249},"{",[171,715,716],{"class":181},"values[",[171,718,46],{"class":249},[171,720,721],{"class":181},"]",[171,723,724],{"class":177},":,",[171,726,727],{"class":249},"}",[171,729,730],{"class":242},", median ",[171,732,713],{"class":249},[171,734,716],{"class":181},[171,736,737],{"class":249},"len",[171,739,740],{"class":181},"(values) ",[171,742,743],{"class":177},"\u002F\u002F",[171,745,746],{"class":249}," 2",[171,748,721],{"class":181},[171,750,724],{"class":177},[171,752,727],{"class":249},[171,754,755],{"class":242},", max ",[171,757,713],{"class":249},[171,759,716],{"class":181},[171,761,673],{"class":177},[171,763,676],{"class":249},[171,765,721],{"class":181},[171,767,724],{"class":177},[171,769,727],{"class":249},[171,771,772],{"class":242},", ratio ",[171,774,713],{"class":249},[171,776,777],{"class":181},"ratio",[171,779,780],{"class":177},":.0f",[171,782,727],{"class":249},[171,784,785],{"class":242},":1\"",[171,787,401],{"class":181},[14,789,790,792],{},[547,791,549],{}," The ratio of largest to smallest value tells you whether proportional sizing will work. Up to about 50:1, area scaling gives readable results; beyond a few hundred to one, the smallest diagrams vanish next to the largest, and you need one of the strategies below.",[124,794,796],{"id":795},"handle-outliers","Handle outliers",[14,798,799],{},"One huge value — a capital city among small towns — sets the upper value, shrinking every other diagram. Three strategies help, each with a cost.",[161,801,803],{"className":163,"code":802,"language":165,"meta":166,"style":166},"import math\n\n# 1. cap at a high percentile; the outlier is drawn at the maximum size\np95 = values[int(len(values) * 0.95)]\nrenderer.setUpperValue(p95)\n\n# 2. scale by a transformed value (state this clearly in the legend)\nrenderer.setClassificationAttributeExpression('sqrt(\"votes_total\")')\nrenderer.setUpperValue(math.sqrt(values[-1]))\n\n# 3. keep proportional sizing but show outliers differently\n#    (e.g. a separate inset map at a smaller scale)\nconstituencies.triggerRepaint()\n",[168,804,805,812,816,822,848,853,857,862,871,883,887,892,897],{"__ignoreMap":166},[171,806,807,809],{"class":173,"line":174},[171,808,185],{"class":177},[171,810,811],{"class":181}," math\n",[171,813,814],{"class":173,"line":191},[171,815,227],{"emptyLinePlaceholder":226},[171,817,818],{"class":173,"line":197},[171,819,821],{"class":820},"sjoCn","# 1. cap at a high percentile; the outlier is drawn at the maximum size\n",[171,823,824,827,829,831,834,836,838,840,843,846],{"class":173,"line":210},[171,825,826],{"class":181},"p95 ",[171,828,236],{"class":177},[171,830,670],{"class":181},[171,832,833],{"class":249},"int",[171,835,704],{"class":181},[171,837,737],{"class":249},[171,839,740],{"class":181},[171,841,842],{"class":177},"*",[171,844,845],{"class":249}," 0.95",[171,847,343],{"class":181},[171,849,850],{"class":173,"line":223},[171,851,852],{"class":181},"renderer.setUpperValue(p95)\n",[171,854,855],{"class":173,"line":230},[171,856,227],{"emptyLinePlaceholder":226},[171,858,859],{"class":173,"line":255},[171,860,861],{"class":820},"# 2. scale by a transformed value (state this clearly in the legend)\n",[171,863,864,866,869],{"class":173,"line":260},[171,865,439],{"class":181},[171,867,868],{"class":242},"'sqrt(\"votes_total\")'",[171,870,401],{"class":181},[171,872,873,876,878,880],{"class":173,"line":271},[171,874,875],{"class":181},"renderer.setUpperValue(math.sqrt(values[",[171,877,673],{"class":177},[171,879,676],{"class":249},[171,881,882],{"class":181},"]))\n",[171,884,885],{"class":173,"line":302},[171,886,227],{"emptyLinePlaceholder":226},[171,888,889],{"class":173,"line":346},[171,890,891],{"class":820},"# 3. keep proportional sizing but show outliers differently\n",[171,893,894],{"class":173,"line":376},[171,895,896],{"class":820},"#    (e.g. a separate inset map at a smaller scale)\n",[171,898,899],{"class":173,"line":387},[171,900,543],{"class":181},[14,902,903,905],{},[547,904,549],{}," Capping at the 95th percentile keeps most diagrams well sized; anything above the cap is drawn at the maximum size, which understates the outliers — label them with their value. Scaling by the square root compresses the range, but readers assume proportionality, so a transformed scale must be stated clearly. Often the best answer is cartographic rather than numeric: an inset for the dense city, where diagrams can be drawn at a larger scale, with proportional sizing everywhere. Choose one strategy and be explicit about it.",[124,907,909],{"id":908},"share-one-scale-across-maps","Share one scale across maps",[14,911,912],{},"A series of maps — the same constituencies in three elections, the same districts in several months — must use the same scale, or readers will compare sizes that do not mean the same thing.",[14,914,915],{},[29,916,919,922,925,927,930,934,940,944,948,951,954,957,960,963,967,969,971],{"viewBox":917,"role":32,"ariaLabel":918,"xmlns":34},"0 0 760 230","Three maps in a series using one shared upper value so that equal diagram sizes mean equal values across years",[36,920,921],{},"One scale across a series",[40,923,924],{},"Three maps for three years. With each map scaled to its own maximum, a diagram of the same size means different values in each year. With one upper value taken from the maximum over all years, the same size means the same value everywhere and changes between years become visible.",[44,926],{"x":46,"y":46,"width":47,"height":79,"fill":49},[51,928,929],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Same size, same value, every year",[44,931],{"x":61,"y":81,"width":90,"height":932,"rx":65,"fill":66,"stroke":67,"style":933},"140","stroke-width:2",[51,935,939],{"x":936,"y":937,"style":938,"fill":56,"textAnchor":57},"135","103.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","2018",[51,941,943],{"x":936,"y":942,"style":85,"fill":67,"textAnchor":57},"129.78","upper value =",[51,945,947],{"x":936,"y":946,"style":85,"fill":67,"textAnchor":57},"155.78","max over all years",[44,949],{"x":950,"y":81,"width":90,"height":932,"rx":65,"fill":66,"stroke":67,"style":933},"269",[51,952,953],{"x":53,"y":937,"style":938,"fill":56,"textAnchor":57},"2022",[51,955,956],{"x":53,"y":942,"style":85,"fill":67,"textAnchor":57},"same upper value",[51,958,959],{"x":53,"y":946,"style":85,"fill":67,"textAnchor":57},"same upper size",[44,961],{"x":962,"y":81,"width":90,"height":932,"rx":65,"fill":66,"stroke":67,"style":933},"514",[51,964,966],{"x":965,"y":937,"style":938,"fill":56,"textAnchor":57},"625","2026",[51,968,956],{"x":965,"y":942,"style":85,"fill":67,"textAnchor":57},[51,970,959],{"x":965,"y":946,"style":85,"fill":67,"textAnchor":57},[51,972,974],{"x":53,"y":973,"style":85,"fill":67,"textAnchor":57},"220","compute the maximum across the whole series, not per map",[161,976,978],{"className":163,"code":977,"language":165,"meta":166,"style":166},"series = {year: QgsProject.instance().mapLayersByName(f\"constituencies_{year}\")[0]\n          for year in (2018, 2022, 2026)}\nseries_max = max(f[\"votes_total\"] or 0 for lyr in series.values() for f in lyr.getFeatures())\n\nfor year, lyr in series.items():\n    r = renderer.clone()\n    r.setUpperValue(series_max)\n    lyr.setDiagramRenderer(r)\n    lyr.setDiagramLayerSettings(QgsDiagramLayerSettings())\n    lyr.triggerRepaint()\nprint(\"shared upper value:\", series_max)\n",[168,979,980,1011,1036,1074,1078,1090,1100,1105,1110,1115,1120],{"__ignoreMap":166},[171,981,982,985,987,990,992,995,997,1000,1002,1005,1007,1009],{"class":173,"line":174},[171,983,984],{"class":181},"series ",[171,986,236],{"class":177},[171,988,989],{"class":181}," {year: QgsProject.instance().mapLayersByName(",[171,991,707],{"class":177},[171,993,994],{"class":242},"\"constituencies_",[171,996,713],{"class":249},[171,998,999],{"class":181},"year",[171,1001,727],{"class":249},[171,1003,1004],{"class":242},"\"",[171,1006,246],{"class":181},[171,1008,46],{"class":249},[171,1010,252],{"class":181},[171,1012,1013,1016,1019,1021,1023,1025,1027,1029,1031,1033],{"class":173,"line":191},[171,1014,1015],{"class":177},"          for",[171,1017,1018],{"class":181}," year ",[171,1020,319],{"class":177},[171,1022,322],{"class":181},[171,1024,939],{"class":249},[171,1026,142],{"class":181},[171,1028,953],{"class":249},[171,1030,142],{"class":181},[171,1032,966],{"class":249},[171,1034,1035],{"class":181},")}\n",[171,1037,1038,1041,1043,1045,1047,1049,1051,1053,1055,1057,1060,1062,1065,1067,1069,1071],{"class":173,"line":197},[171,1039,1040],{"class":181},"series_max ",[171,1042,236],{"class":177},[171,1044,684],{"class":249},[171,1046,466],{"class":181},[171,1048,469],{"class":242},[171,1050,472],{"class":181},[171,1052,475],{"class":177},[171,1054,478],{"class":249},[171,1056,481],{"class":177},[171,1058,1059],{"class":181}," lyr ",[171,1061,319],{"class":177},[171,1063,1064],{"class":181}," series.values() ",[171,1066,313],{"class":177},[171,1068,484],{"class":181},[171,1070,319],{"class":177},[171,1072,1073],{"class":181}," lyr.getFeatures())\n",[171,1075,1076],{"class":173,"line":210},[171,1077,227],{"emptyLinePlaceholder":226},[171,1079,1080,1082,1085,1087],{"class":173,"line":223},[171,1081,313],{"class":177},[171,1083,1084],{"class":181}," year, lyr ",[171,1086,319],{"class":177},[171,1088,1089],{"class":181}," series.items():\n",[171,1091,1092,1095,1097],{"class":173,"line":230},[171,1093,1094],{"class":181},"    r ",[171,1096,236],{"class":177},[171,1098,1099],{"class":181}," renderer.clone()\n",[171,1101,1102],{"class":173,"line":255},[171,1103,1104],{"class":181},"    r.setUpperValue(series_max)\n",[171,1106,1107],{"class":173,"line":260},[171,1108,1109],{"class":181},"    lyr.setDiagramRenderer(r)\n",[171,1111,1112],{"class":173,"line":271},[171,1113,1114],{"class":181},"    lyr.setDiagramLayerSettings(QgsDiagramLayerSettings())\n",[171,1116,1117],{"class":173,"line":302},[171,1118,1119],{"class":181},"    lyr.triggerRepaint()\n",[171,1121,1122,1124,1126,1129],{"class":173,"line":346},[171,1123,701],{"class":249},[171,1125,704],{"class":181},[171,1127,1128],{"class":242},"\"shared upper value:\"",[171,1130,1131],{"class":181},", series_max)\n",[14,1133,1134,1136],{},[547,1135,549],{}," Cloning the renderer gives each layer its own copy with identical settings, and the upper value comes from the maximum across every layer in the series. Recording the shared value in each map's caption or legend lets a reader of any single map understand its scale. When the series is an atlas over one layer filtered by year, a single renderer with a fixed upper value achieves the same thing automatically.",[124,1138,1140],{"id":1139},"use-stepped-sizes-from-an-expression","Use stepped sizes from an expression",[14,1142,1143],{},"Continuous scaling shows every difference but is hard to read precisely. Size classes — three or four fixed sizes for value bands — are easier to read and to explain in a legend, at the cost of hiding differences within a class. The interpolated renderer produces them when its classification expression returns a class number instead of the raw value.",[161,1145,1147],{"className":163,"code":1146,"language":165,"meta":166,"style":166},"stepped = renderer.clone()\nstepped.setClassificationAttributeExpression(\n    'CASE WHEN \"votes_total\" \u003C 20000 THEN 1 '\n    'WHEN \"votes_total\" \u003C 50000 THEN 2 '\n    'WHEN \"votes_total\" \u003C 100000 THEN 3 ELSE 4 END')\nstepped.setLowerValue(0)\nstepped.setUpperValue(4)\nstepped.setLowerSize(QSizeF(0, 0))\nstepped.setUpperSize(QSizeF(16, 16))\nconstituencies.setDiagramRenderer(stepped)\nconstituencies.triggerRepaint()\n",[168,1148,1149,1158,1163,1168,1173,1180,1189,1199,1212,1226,1231],{"__ignoreMap":166},[171,1150,1151,1154,1156],{"class":173,"line":174},[171,1152,1153],{"class":181},"stepped ",[171,1155,236],{"class":177},[171,1157,1099],{"class":181},[171,1159,1160],{"class":173,"line":191},[171,1161,1162],{"class":181},"stepped.setClassificationAttributeExpression(\n",[171,1164,1165],{"class":173,"line":197},[171,1166,1167],{"class":242},"    'CASE WHEN \"votes_total\" \u003C 20000 THEN 1 '\n",[171,1169,1170],{"class":173,"line":210},[171,1171,1172],{"class":242},"    'WHEN \"votes_total\" \u003C 50000 THEN 2 '\n",[171,1174,1175,1178],{"class":173,"line":223},[171,1176,1177],{"class":242},"    'WHEN \"votes_total\" \u003C 100000 THEN 3 ELSE 4 END'",[171,1179,401],{"class":181},[171,1181,1182,1185,1187],{"class":173,"line":230},[171,1183,1184],{"class":181},"stepped.setLowerValue(",[171,1186,46],{"class":249},[171,1188,401],{"class":181},[171,1190,1191,1194,1197],{"class":173,"line":255},[171,1192,1193],{"class":181},"stepped.setUpperValue(",[171,1195,1196],{"class":249},"4",[171,1198,401],{"class":181},[171,1200,1201,1204,1206,1208,1210],{"class":173,"line":260},[171,1202,1203],{"class":181},"stepped.setLowerSize(QSizeF(",[171,1205,46],{"class":249},[171,1207,142],{"class":181},[171,1209,46],{"class":249},[171,1211,504],{"class":181},[171,1213,1214,1217,1220,1222,1224],{"class":173,"line":271},[171,1215,1216],{"class":181},"stepped.setUpperSize(QSizeF(",[171,1218,1219],{"class":249},"16",[171,1221,142],{"class":181},[171,1223,1219],{"class":249},[171,1225,504],{"class":181},[171,1227,1228],{"class":173,"line":302},[171,1229,1230],{"class":181},"constituencies.setDiagramRenderer(stepped)\n",[171,1232,1233],{"class":173,"line":346},[171,1234,543],{"class":181},[14,1236,1237,1239,1240,1242,1243,1247],{},[547,1238,549],{}," The expression maps each feature to a class from 1 to 4, and the renderer maps 0–4 to 0–16 mm, so the classes get four distinct sizes. With ",[168,1241,553],{}," still on, each step adds the same area, which keeps the classes visually even. Choose class breaks that mean something to the reader — round numbers, policy thresholds — rather than letting an algorithm pick them. The legend must then show the classes with their bounds, not a continuous scale, because sizes no longer correspond to exact values. For point layers where you only need sized symbols rather than charts, the symbol-level approach in ",[21,1244,1246],{"href":1245},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-proportional-symbol-map-pyqgis\u002F","proportional symbol maps"," is often simpler.",[124,1249,1251],{"id":1250},"add-a-size-legend","Add a size legend",[14,1253,1254],{},"Readers need a key to translate size back into numbers. The interpolated renderer supports a size legend that draws reference diagrams for chosen values.",[161,1256,1258],{"className":163,"code":1257,"language":165,"meta":166,"style":166},"from qgis.core import QgsDataDefinedSizeLegend\n\nlegend = QgsDataDefinedSizeLegend()\nlegend.setTitle(\"votes cast\")\nlegend.setLegendType(QgsDataDefinedSizeLegend.LegendCollapsed)\nrenderer.setDataDefinedSizeLegend(legend)\nrenderer.setAttributeLegend(True)\nconstituencies.setDiagramRenderer(renderer)\nconstituencies.triggerRepaint()\n",[168,1259,1260,1271,1275,1285,1295,1300,1305,1315,1319],{"__ignoreMap":166},[171,1261,1262,1264,1266,1268],{"class":173,"line":174},[171,1263,178],{"class":177},[171,1265,182],{"class":181},[171,1267,185],{"class":177},[171,1269,1270],{"class":181}," QgsDataDefinedSizeLegend\n",[171,1272,1273],{"class":173,"line":191},[171,1274,227],{"emptyLinePlaceholder":226},[171,1276,1277,1280,1282],{"class":173,"line":197},[171,1278,1279],{"class":181},"legend ",[171,1281,236],{"class":177},[171,1283,1284],{"class":181}," QgsDataDefinedSizeLegend()\n",[171,1286,1287,1290,1293],{"class":173,"line":210},[171,1288,1289],{"class":181},"legend.setTitle(",[171,1291,1292],{"class":242},"\"votes cast\"",[171,1294,401],{"class":181},[171,1296,1297],{"class":173,"line":223},[171,1298,1299],{"class":181},"legend.setLegendType(QgsDataDefinedSizeLegend.LegendCollapsed)\n",[171,1301,1302],{"class":173,"line":230},[171,1303,1304],{"class":181},"renderer.setDataDefinedSizeLegend(legend)\n",[171,1306,1307,1310,1313],{"class":173,"line":255},[171,1308,1309],{"class":181},"renderer.setAttributeLegend(",[171,1311,1312],{"class":249},"True",[171,1314,401],{"class":181},[171,1316,1317],{"class":173,"line":260},[171,1318,531],{"class":181},[171,1320,1321],{"class":173,"line":271},[171,1322,543],{"class":181},[14,1324,1325,1327,1328,1332],{},[547,1326,549],{}," A collapsed size legend draws nested circles for representative values, the familiar compact key for proportional symbols. The attribute legend adds the category colours. Both appear in the Layers panel and in a print layout legend. ",[21,1329,1331],{"href":1330},"\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fadd-diagram-legend-and-text-diagrams-pyqgis\u002F","Adding diagram legends"," covers choosing the reference values and styling the result.",[124,1334,1336],{"id":1335},"check-sizes-at-print-scale","Check sizes at print scale",[14,1338,1339],{},"Diagram sizes are in millimetres on the page, so their relationship to the features underneath changes with map scale. A layout at 1:50,000 and one at 1:250,000 show the same 18 mm pies over very different areas. Before finalising sizes, export a test page at the real scale and look for two things: diagrams overlapping so much that collision handling drops many of them, and diagrams so small that slices or segments disappear. Adjust the upper size first, since it scales every diagram at once; only then revisit the lower size or outlier handling. Keeping the final values in a small dictionary at the top of the script — upper value, upper size, lower size — makes it easy to produce variants of the same map at different print scales without hunting through code.",[124,1341,1343],{"id":1342},"qgis-version-compatibility","QGIS version compatibility",[14,1345,1346,1347,1349,1350,1353,1354,1357],{},"The interpolated renderer and ",[168,1348,553],{}," work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. Size legends for diagrams (",[168,1351,1352],{},"setDataDefinedSizeLegend",") exist since 3.4. On QGIS 4, enum members are scoped, for example ",[168,1355,1356],{},"QgsDataDefinedSizeLegend.LegendType.LegendCollapsed",".",[124,1359,1361],{"id":1360},"troubleshooting","Troubleshooting",[129,1363,1364,1373,1379,1385],{},[132,1365,1366,1369,1370,1357],{},[547,1367,1368],{},"Large values look enormous."," Area scaling is off; set ",[168,1371,1372],{},"scaleByArea = True",[132,1374,1375,1378],{},[547,1376,1377],{},"Small features have no diagram."," Their size interpolates to nearly zero; accept it, or set a small lower size and say so.",[132,1380,1381,1384],{},[547,1382,1383],{},"Maps in a series do not compare."," Each was scaled to its own maximum; share the upper value.",[132,1386,1387,1390],{},[547,1388,1389],{},"Diagrams exceed the maximum size."," Values above the upper value are drawn at the upper size or beyond; recompute or cap deliberately.",[124,1392,1394],{"id":1393},"conclusion","Conclusion",[14,1396,1397],{},"Use the linearly interpolated renderer with zero-based ranges and area scaling, choose the upper size from the space available and the upper value from the data, handle outliers by capping, transforming or insetting — and say which — share one upper value across a series, and add a size legend so readers can read values back.",[124,1399,1401],{"id":1400},"frequently-asked-questions","Frequently Asked Questions",[14,1403,1404,1407],{},[547,1405,1406],{},"Should bars scale by area too?","\nNo. Bars encode values by length; area scaling applies to pies and other two-dimensional diagrams.",[14,1409,1410,1413],{},[547,1411,1412],{},"What maximum size works for print?","\nTypically 15–25 mm at the map's print scale, depending on feature density.",[14,1415,1416,1419],{},[547,1417,1418],{},"Can I size by one field and colour by others?","\nYes — that is the normal case: size by the total, slices or segments by the parts.",[14,1421,1422,1425],{},[547,1423,1424],{},"Is there a minimum readable pie?","\nAround 4 mm; below that slices are hard to see. Hide or aggregate features that small.",[124,1427,1429],{"id":1428},"related","Related",[129,1431,1432,1437,1442,1447,1452],{},[132,1433,1434,1436],{},[21,1435,24],{"href":23}," — the guide this recipe belongs to",[132,1438,1439],{},[21,1440,1441],{"href":140},"Add Pie Chart Diagrams to a Layer in PyQGIS",[132,1443,1444],{},[21,1445,1446],{"href":1330},"Add Diagram Legends and Text Diagrams in PyQGIS",[132,1448,1449],{},[21,1450,1451],{"href":1245},"Create a Proportional Symbol Map in PyQGIS",[132,1453,1454],{},[21,1455,1457],{"href":1456},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fdata-defined-symbol-size-expression-pyqgis\u002F","Data-Defined Symbol Size with Expressions",[1459,1460,1461],"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 .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);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":166,"searchDepth":191,"depth":191,"links":1463},[1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474,1475,1476],{"id":126,"depth":191,"text":127},{"id":155,"depth":191,"text":156},{"id":557,"depth":191,"text":558},{"id":795,"depth":191,"text":796},{"id":908,"depth":191,"text":909},{"id":1139,"depth":191,"text":1140},{"id":1250,"depth":191,"text":1251},{"id":1335,"depth":191,"text":1336},{"id":1342,"depth":191,"text":1343},{"id":1360,"depth":191,"text":1361},{"id":1393,"depth":191,"text":1394},{"id":1400,"depth":191,"text":1401},{"id":1428,"depth":191,"text":1429},"Scale diagram size by a field or expression with the linearly interpolated renderer — choosing value and size ranges, scaling by area rather than diameter, handling outliers, sharing one scale across maps, and data-defined sizes for full control.","md",{"slug":1480,"type":1481,"breadcrumb":1482,"datePublished":1483,"dateModified":1483},"size-diagrams-by-attribute-pyqgis","article","Size Diagrams by Attribute","2026-10-02","\u002Fpyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fsize-diagrams-by-attribute-pyqgis",{"title":5,"description":1477},"pyqgis-cartography-visualization\u002Fdiagrams-and-charts-on-maps\u002Fsize-diagrams-by-attribute-pyqgis\u002Findex","N3aFZrtGP67FDXHI0X-WSmroIAAD0bQ_tdGRja4mUJQ",1790966252900]