[{"data":1,"prerenderedAt":1486},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-label-callouts-pyqgis":3},{"id":4,"title":5,"body":6,"description":1475,"extension":1476,"meta":1477,"navigation":195,"path":1482,"seo":1483,"stem":1484,"__hash__":1485},"docs\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-label-callouts-pyqgis\u002Findex.md","Add Label Callouts in PyQGIS",{"type":7,"value":8,"toc":1460},"minimark",[9,13,17,26,121,126,136,140,143,429,440,444,447,534,714,719,723,726,789,794,798,801,886,1029,1034,1038,1041,1220,1225,1229,1232,1236,1239,1315,1325,1329,1356,1360,1386,1390,1393,1397,1403,1409,1415,1421,1425,1456],[10,11,5],"h1",{"id":12},"add-label-callouts-in-pyqgis",[14,15,16],"p",{},"When features are crowded — wells in a field, shops along a street, buildings in a dense block — labels cannot sit next to every feature. They get pushed aside, and without a visual link the reader cannot tell which label belongs to which point. Callouts solve this with a leader line from the label to the feature. QGIS draws them automatically when a label is placed away from its anchor, in several styles, and they work with both automatic placement and labels moved by hand.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002F","Labeling & Annotations",". It enables simple callouts, chooses between callout styles, controls where lines attach, hides callouts for labels that sit close enough, uses data-defined label positions with callouts, and styles the leader lines.",[14,27,28],{},[29,30,35,39,43,51,60,68,72,75,78,82,88,91,94,97,100,107,110,113,117],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"122 0 472 256","img","Five crowded wells with labels placed in a column to the side, each connected to its well by a thin leader line","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Leader lines link displaced labels",[40,41,42],"desc",{},"A cluster of five wells close together. Their labels are placed in a column to the right where there is space, and each label is connected to its well by a thin leader line. Without the lines, readers would have to guess which label belongs to which well.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"122","0","472","256","#f6f3ea",[52,53,59],"text",{"x":54,"y":55,"style":56,"fill":57,"textAnchor":58},"358","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","Labels where there is room, lines to where they belong",[61,62],"circle",{"cx":63,"cy":64,"r":65,"fill":66,"stroke":57,"style":67},"160","110","6","#2563eb","stroke-width:1",[61,69],{"cx":70,"cy":71,"r":65,"fill":66,"stroke":57,"style":67},"190","130",[61,73],{"cx":74,"cy":63,"r":65,"fill":66,"stroke":57,"style":67},"170",[61,76],{"cx":77,"cy":74,"r":65,"fill":66,"stroke":57,"style":67},"210",[61,79],{"cx":80,"cy":81,"r":65,"fill":66,"stroke":57,"style":67},"185","200",[83,84],"line",{"x1":63,"y1":64,"x2":85,"y2":86,"stroke":87,"style":67},"420","80","#2f3b35",[83,89],{"x1":70,"y1":71,"x2":85,"y2":90,"stroke":87,"style":67},"118",[83,92],{"x1":74,"y1":63,"x2":85,"y2":93,"stroke":87,"style":67},"156",[83,95],{"x1":77,"y1":74,"x2":85,"y2":96,"stroke":87,"style":67},"194",[83,98],{"x1":80,"y1":81,"x2":85,"y2":99,"stroke":87,"style":67},"232",[52,101,106],{"x":102,"y":103,"style":104,"fill":87,"textAnchor":105},"428","84","text-anchor:start;font-size:10.5px;font-family:sans-serif","start","W-101  12.4 m",[52,108,109],{"x":102,"y":46,"style":104,"fill":87,"textAnchor":105},"W-102  14.0 m",[52,111,112],{"x":102,"y":63,"style":104,"fill":87,"textAnchor":105},"W-103  9.8 m",[52,114,116],{"x":102,"y":115,"style":104,"fill":87,"textAnchor":105},"198","W-104  11.1 m",[52,118,120],{"x":102,"y":119,"style":104,"fill":87,"textAnchor":105},"236","W-105  13.7 m",[122,123,125],"h2",{"id":124},"prerequisites","Prerequisites",[127,128,129,133],"ul",{},[130,131,132],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series. Callouts arrived in QGIS 3.10, with more styles in later releases.",[130,134,135],{},"A point, line or polygon layer with labels configured.",[122,137,139],{"id":138},"enable-a-simple-callout","Enable a simple callout",[14,141,142],{},"Callouts belong to the label settings. A simple callout draws a straight line from the label to the feature whenever the label is not directly at its anchor.",[144,145,150],"pre",{"className":146,"code":147,"language":148,"meta":149,"style":149},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsProject, QgsPalLayerSettings, QgsTextFormat, QgsSimpleLineCallout,\n                       QgsLineSymbol, QgsVectorLayerSimpleLabeling, QgsUnitTypes)\nfrom qgis.PyQt.QtGui import QFont\n\nwells = QgsProject.instance().mapLayersByName(\"wells\")[0]\n\nsettings = QgsPalLayerSettings()\nsettings.fieldName = \"well_id\"\nsettings.placement = QgsPalLayerSettings.AroundPoint\nsettings.dist = 4\nsettings.distUnits = QgsUnitTypes.RenderMillimeters\nfmt = QgsTextFormat()\nfmt.setFont(QFont(\"Noto Sans\"))\nfmt.setSize(8)\nsettings.setFormat(fmt)\n\ncallout = QgsSimpleLineCallout()\ncallout.setEnabled(True)\ncallout.setLineSymbol(QgsLineSymbol.createSimple({\"color\": \"#2f3b35\", \"width\": \"0.2\"}))\ncallout.setMinimumLength(2)\ncallout.setMinimumLengthUnit(QgsUnitTypes.RenderMillimeters)\nsettings.setCallout(callout)\n\nwells.setLabeling(QgsVectorLayerSimpleLabeling(settings))\nwells.setLabelsEnabled(True)\nwells.triggerRepaint()\n","python","",[151,152,153,171,177,190,197,222,227,238,249,260,271,282,293,305,317,323,328,339,350,379,390,396,402,407,413,423],"code",{"__ignoreMap":149},[154,155,157,161,165,168],"span",{"class":83,"line":156},1,[154,158,160],{"class":159},"snl16","from",[154,162,164],{"class":163},"s95oV"," qgis.core ",[154,166,167],{"class":159},"import",[154,169,170],{"class":163}," (QgsProject, QgsPalLayerSettings, QgsTextFormat, QgsSimpleLineCallout,\n",[154,172,174],{"class":83,"line":173},2,[154,175,176],{"class":163},"                       QgsLineSymbol, QgsVectorLayerSimpleLabeling, QgsUnitTypes)\n",[154,178,180,182,185,187],{"class":83,"line":179},3,[154,181,160],{"class":159},[154,183,184],{"class":163}," qgis.PyQt.QtGui ",[154,186,167],{"class":159},[154,188,189],{"class":163}," QFont\n",[154,191,193],{"class":83,"line":192},4,[154,194,196],{"emptyLinePlaceholder":195},true,"\n",[154,198,200,203,206,209,213,216,219],{"class":83,"line":199},5,[154,201,202],{"class":163},"wells ",[154,204,205],{"class":159},"=",[154,207,208],{"class":163}," QgsProject.instance().mapLayersByName(",[154,210,212],{"class":211},"sU2Wk","\"wells\"",[154,214,215],{"class":163},")[",[154,217,47],{"class":218},"sDLfK",[154,220,221],{"class":163},"]\n",[154,223,225],{"class":83,"line":224},6,[154,226,196],{"emptyLinePlaceholder":195},[154,228,230,233,235],{"class":83,"line":229},7,[154,231,232],{"class":163},"settings ",[154,234,205],{"class":159},[154,236,237],{"class":163}," QgsPalLayerSettings()\n",[154,239,241,244,246],{"class":83,"line":240},8,[154,242,243],{"class":163},"settings.fieldName ",[154,245,205],{"class":159},[154,247,248],{"class":211}," \"well_id\"\n",[154,250,252,255,257],{"class":83,"line":251},9,[154,253,254],{"class":163},"settings.placement ",[154,256,205],{"class":159},[154,258,259],{"class":163}," QgsPalLayerSettings.AroundPoint\n",[154,261,263,266,268],{"class":83,"line":262},10,[154,264,265],{"class":163},"settings.dist ",[154,267,205],{"class":159},[154,269,270],{"class":218}," 4\n",[154,272,274,277,279],{"class":83,"line":273},11,[154,275,276],{"class":163},"settings.distUnits ",[154,278,205],{"class":159},[154,280,281],{"class":163}," QgsUnitTypes.RenderMillimeters\n",[154,283,285,288,290],{"class":83,"line":284},12,[154,286,287],{"class":163},"fmt ",[154,289,205],{"class":159},[154,291,292],{"class":163}," QgsTextFormat()\n",[154,294,296,299,302],{"class":83,"line":295},13,[154,297,298],{"class":163},"fmt.setFont(QFont(",[154,300,301],{"class":211},"\"Noto Sans\"",[154,303,304],{"class":163},"))\n",[154,306,308,311,314],{"class":83,"line":307},14,[154,309,310],{"class":163},"fmt.setSize(",[154,312,313],{"class":218},"8",[154,315,316],{"class":163},")\n",[154,318,320],{"class":83,"line":319},15,[154,321,322],{"class":163},"settings.setFormat(fmt)\n",[154,324,326],{"class":83,"line":325},16,[154,327,196],{"emptyLinePlaceholder":195},[154,329,331,334,336],{"class":83,"line":330},17,[154,332,333],{"class":163},"callout ",[154,335,205],{"class":159},[154,337,338],{"class":163}," QgsSimpleLineCallout()\n",[154,340,342,345,348],{"class":83,"line":341},18,[154,343,344],{"class":163},"callout.setEnabled(",[154,346,347],{"class":218},"True",[154,349,316],{"class":163},[154,351,353,356,359,362,365,368,371,373,376],{"class":83,"line":352},19,[154,354,355],{"class":163},"callout.setLineSymbol(QgsLineSymbol.createSimple({",[154,357,358],{"class":211},"\"color\"",[154,360,361],{"class":163},": ",[154,363,364],{"class":211},"\"#2f3b35\"",[154,366,367],{"class":163},", ",[154,369,370],{"class":211},"\"width\"",[154,372,361],{"class":163},[154,374,375],{"class":211},"\"0.2\"",[154,377,378],{"class":163},"}))\n",[154,380,382,385,388],{"class":83,"line":381},20,[154,383,384],{"class":163},"callout.setMinimumLength(",[154,386,387],{"class":218},"2",[154,389,316],{"class":163},[154,391,393],{"class":83,"line":392},21,[154,394,395],{"class":163},"callout.setMinimumLengthUnit(QgsUnitTypes.RenderMillimeters)\n",[154,397,399],{"class":83,"line":398},22,[154,400,401],{"class":163},"settings.setCallout(callout)\n",[154,403,405],{"class":83,"line":404},23,[154,406,196],{"emptyLinePlaceholder":195},[154,408,410],{"class":83,"line":409},24,[154,411,412],{"class":163},"wells.setLabeling(QgsVectorLayerSimpleLabeling(settings))\n",[154,414,416,419,421],{"class":83,"line":415},25,[154,417,418],{"class":163},"wells.setLabelsEnabled(",[154,420,347],{"class":218},[154,422,316],{"class":163},[154,424,426],{"class":83,"line":425},26,[154,427,428],{"class":163},"wells.triggerRepaint()\n",[14,430,431,435,436,439],{},[432,433,434],"strong",{},"Breakdown:"," Placing labels around the point at a 4 mm distance gives the labelling engine room to move them, which is when callouts appear. A thin, dark leader line keeps the link visible without competing with the data. ",[151,437,438],{},"setMinimumLength"," suppresses callouts shorter than 2 mm, so labels that sit right next to their feature are not decorated with a pointless stub. The callout is drawn by the labelling engine, so it is included in exports and layouts automatically.",[122,441,443],{"id":442},"choose-a-callout-style","Choose a callout style",[14,445,446],{},"Several callout types exist, each suited to a different map style.",[14,448,449],{},[29,450,453,456,459,463,467,475,481,486,490,495,499,502,505,509,512,515,518,524,528,531],{"viewBox":451,"role":32,"ariaLabel":452,"xmlns":34},"0 0 760 250","Four callout styles: simple straight lines, Manhattan right-angled lines, curved arcs and balloons around the label",[36,454,455],{},"Callout styles",[40,457,458],{},"Simple: a straight line from feature to label. Manhattan: horizontal and vertical segments, tidy on technical drawings and plans. Curved: a gentle arc, softer for general maps. Balloon: a speech-bubble shape around the label with a wedge pointing to the feature, suited to highlighting a few features.",[44,460],{"x":47,"y":47,"width":461,"height":462,"fill":50},"760","250",[52,464,466],{"x":465,"y":55,"style":56,"fill":57,"textAnchor":58},"380","Straight, right-angled, curved, balloon",[44,468],{"x":469,"y":470,"width":471,"height":74,"rx":313,"fill":472,"stroke":473,"style":474},"24","56","168","#fffdf7","#59645f","stroke-width:2",[52,476,480],{"x":477,"y":478,"style":479,"fill":57,"textAnchor":58},"108","116.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","simple",[52,482,485],{"x":477,"y":483,"style":484,"fill":473,"textAnchor":58},"144.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","straight line",[52,487,489],{"x":477,"y":488,"style":484,"fill":473,"textAnchor":58},"172.78","general use",[44,491],{"x":492,"y":470,"width":471,"height":74,"rx":313,"fill":493,"stroke":494,"style":474},"206","#eef7f4","#0f766e",[52,496,498],{"x":497,"y":478,"style":479,"fill":494,"textAnchor":58},"290","Manhattan",[52,500,501],{"x":497,"y":483,"style":484,"fill":473,"textAnchor":58},"right angles",[52,503,504],{"x":497,"y":488,"style":484,"fill":473,"textAnchor":58},"plans, schematics",[44,506],{"x":507,"y":470,"width":471,"height":74,"rx":313,"fill":508,"stroke":66,"style":474},"388","#eff3ff",[52,510,511],{"x":48,"y":478,"style":479,"fill":66,"textAnchor":58},"curved",[52,513,514],{"x":48,"y":483,"style":484,"fill":473,"textAnchor":58},"soft arc",[52,516,517],{"x":48,"y":488,"style":484,"fill":473,"textAnchor":58},"general maps",[44,519],{"x":520,"y":470,"width":521,"height":74,"rx":313,"fill":522,"stroke":523,"style":474},"570","166","#fdf2e2","#b45309",[52,525,527],{"x":526,"y":478,"style":479,"fill":523,"textAnchor":58},"653","balloon",[52,529,530],{"x":526,"y":483,"style":484,"fill":473,"textAnchor":58},"bubble + wedge",[52,532,533],{"x":526,"y":488,"style":484,"fill":473,"textAnchor":58},"a few highlights",[144,535,537],{"className":146,"code":536,"language":148,"meta":149,"style":149},"from qgis.core import QgsManhattanLineCallout, QgsCurvedLineCallout, QgsBalloonCallout, QgsFillSymbol\n\nmanhattan = QgsManhattanLineCallout()\nmanhattan.setEnabled(True)\nmanhattan.setLineSymbol(QgsLineSymbol.createSimple({\"color\": \"#2f3b35\", \"width\": \"0.2\"}))\n\ncurved = QgsCurvedLineCallout()\ncurved.setEnabled(True)\ncurved.setCurvature(0.15)\n\nballoon = QgsBalloonCallout()\nballoon.setEnabled(True)\nballoon.setFillSymbol(QgsFillSymbol.createSimple(\n    {\"color\": \"255,253,247,235\", \"outline_color\": \"#59645f\", \"outline_width\": \"0.2\"}))\nballoon.setWedgeWidth(2)\n\nsettings.setCallout(manhattan)\nwells.setLabeling(QgsVectorLayerSimpleLabeling(settings))\nwells.triggerRepaint()\n",[151,538,539,550,554,564,573,594,598,608,617,627,631,641,650,655,688,697,701,706,710],{"__ignoreMap":149},[154,540,541,543,545,547],{"class":83,"line":156},[154,542,160],{"class":159},[154,544,164],{"class":163},[154,546,167],{"class":159},[154,548,549],{"class":163}," QgsManhattanLineCallout, QgsCurvedLineCallout, QgsBalloonCallout, QgsFillSymbol\n",[154,551,552],{"class":83,"line":173},[154,553,196],{"emptyLinePlaceholder":195},[154,555,556,559,561],{"class":83,"line":179},[154,557,558],{"class":163},"manhattan ",[154,560,205],{"class":159},[154,562,563],{"class":163}," QgsManhattanLineCallout()\n",[154,565,566,569,571],{"class":83,"line":192},[154,567,568],{"class":163},"manhattan.setEnabled(",[154,570,347],{"class":218},[154,572,316],{"class":163},[154,574,575,578,580,582,584,586,588,590,592],{"class":83,"line":199},[154,576,577],{"class":163},"manhattan.setLineSymbol(QgsLineSymbol.createSimple({",[154,579,358],{"class":211},[154,581,361],{"class":163},[154,583,364],{"class":211},[154,585,367],{"class":163},[154,587,370],{"class":211},[154,589,361],{"class":163},[154,591,375],{"class":211},[154,593,378],{"class":163},[154,595,596],{"class":83,"line":224},[154,597,196],{"emptyLinePlaceholder":195},[154,599,600,603,605],{"class":83,"line":229},[154,601,602],{"class":163},"curved ",[154,604,205],{"class":159},[154,606,607],{"class":163}," QgsCurvedLineCallout()\n",[154,609,610,613,615],{"class":83,"line":240},[154,611,612],{"class":163},"curved.setEnabled(",[154,614,347],{"class":218},[154,616,316],{"class":163},[154,618,619,622,625],{"class":83,"line":251},[154,620,621],{"class":163},"curved.setCurvature(",[154,623,624],{"class":218},"0.15",[154,626,316],{"class":163},[154,628,629],{"class":83,"line":262},[154,630,196],{"emptyLinePlaceholder":195},[154,632,633,636,638],{"class":83,"line":273},[154,634,635],{"class":163},"balloon ",[154,637,205],{"class":159},[154,639,640],{"class":163}," QgsBalloonCallout()\n",[154,642,643,646,648],{"class":83,"line":284},[154,644,645],{"class":163},"balloon.setEnabled(",[154,647,347],{"class":218},[154,649,316],{"class":163},[154,651,652],{"class":83,"line":295},[154,653,654],{"class":163},"balloon.setFillSymbol(QgsFillSymbol.createSimple(\n",[154,656,657,660,662,664,667,669,672,674,677,679,682,684,686],{"class":83,"line":307},[154,658,659],{"class":163},"    {",[154,661,358],{"class":211},[154,663,361],{"class":163},[154,665,666],{"class":211},"\"255,253,247,235\"",[154,668,367],{"class":163},[154,670,671],{"class":211},"\"outline_color\"",[154,673,361],{"class":163},[154,675,676],{"class":211},"\"#59645f\"",[154,678,367],{"class":163},[154,680,681],{"class":211},"\"outline_width\"",[154,683,361],{"class":163},[154,685,375],{"class":211},[154,687,378],{"class":163},[154,689,690,693,695],{"class":83,"line":319},[154,691,692],{"class":163},"balloon.setWedgeWidth(",[154,694,387],{"class":218},[154,696,316],{"class":163},[154,698,699],{"class":83,"line":325},[154,700,196],{"emptyLinePlaceholder":195},[154,702,703],{"class":83,"line":330},[154,704,705],{"class":163},"settings.setCallout(manhattan)\n",[154,707,708],{"class":83,"line":341},[154,709,412],{"class":163},[154,711,712],{"class":83,"line":352},[154,713,428],{"class":163},[14,715,716,718],{},[432,717,434],{}," Manhattan callouts draw only horizontal and vertical segments, which suits engineering plans and network schematics where everything else is orthogonal. Curved callouts soften the look on general maps; the curvature value bends the line gently. Balloon callouts draw a filled shape around the label with a wedge pointing at the feature — effective for a handful of highlighted features, cluttered for many. All share the common callout properties such as minimum length and anchor settings.",[122,720,722],{"id":721},"control-where-lines-attach","Control where lines attach",[14,724,725],{},"By default the line runs from the nearest point of the label to the feature's point. Anchor and label-anchor settings change both ends.",[144,727,729],{"className":146,"code":728,"language":148,"meta":149,"style":149},"from qgis.core import QgsCallout\n\ncallout.setAnchorPoint(QgsCallout.PoleOfInaccessibility)   # for polygons\ncallout.setLabelAnchorPoint(QgsCallout.LabelPointOnExterior)\ncallout.setOffsetFromAnchor(0.5)\ncallout.setOffsetFromAnchorUnit(QgsUnitTypes.RenderMillimeters)\ncallout.setDrawCalloutToAllParts(False)\nsettings.setCallout(callout)\n",[151,730,731,742,746,755,760,770,775,785],{"__ignoreMap":149},[154,732,733,735,737,739],{"class":83,"line":156},[154,734,160],{"class":159},[154,736,164],{"class":163},[154,738,167],{"class":159},[154,740,741],{"class":163}," QgsCallout\n",[154,743,744],{"class":83,"line":173},[154,745,196],{"emptyLinePlaceholder":195},[154,747,748,751],{"class":83,"line":179},[154,749,750],{"class":163},"callout.setAnchorPoint(QgsCallout.PoleOfInaccessibility)   ",[154,752,754],{"class":753},"sjoCn","# for polygons\n",[154,756,757],{"class":83,"line":192},[154,758,759],{"class":163},"callout.setLabelAnchorPoint(QgsCallout.LabelPointOnExterior)\n",[154,761,762,765,768],{"class":83,"line":199},[154,763,764],{"class":163},"callout.setOffsetFromAnchor(",[154,766,767],{"class":218},"0.5",[154,769,316],{"class":163},[154,771,772],{"class":83,"line":224},[154,773,774],{"class":163},"callout.setOffsetFromAnchorUnit(QgsUnitTypes.RenderMillimeters)\n",[154,776,777,780,783],{"class":83,"line":229},[154,778,779],{"class":163},"callout.setDrawCalloutToAllParts(",[154,781,782],{"class":218},"False",[154,784,316],{"class":163},[154,786,787],{"class":83,"line":240},[154,788,401],{"class":163},[14,790,791,793],{},[432,792,434],{}," For polygon layers, anchoring the callout at the pole of inaccessibility puts the line's end at the polygon's visual centre rather than its centroid, which may lie outside the shape. The label anchor decides where on the label the line starts — nearest point on the label's outline, or a fixed corner or edge centre for a more regular look. A small offset from the anchor leaves a gap so the line does not touch the symbol. For multipart features, drawing to all parts creates one line per part, useful for a label naming several islands.",[122,795,797],{"id":796},"combine-callouts-with-manually-placed-labels","Combine callouts with manually placed labels",[14,799,800],{},"The most common use of callouts in finished maps: a cartographer drags labels to good positions, and callouts connect them. Storing those positions in fields makes them part of the data and reproducible.",[14,802,803],{},[29,804,807,810,813,816,831,834,839,844,848,852,857,860,865,869,873,878,883],{"viewBox":805,"role":32,"ariaLabel":806,"xmlns":34},"0 0 760 196","Label position fields driving data-defined placement for moved labels, with callouts appearing only where labels were displaced",[36,808,809],{},"Manual positions in fields",[40,811,812],{},"Two fields, label_x and label_y, hold the label position for features a cartographer has moved. Data-defined position properties read them; where they are NULL, automatic placement is used. Callouts appear for the moved labels because they are displaced from their anchors, and not for automatically placed ones that sit close.",[44,814],{"x":47,"y":47,"width":461,"height":815,"fill":50},"196",[817,818,819],"defs",{},[820,821,827],"marker",{"id":822,"viewBox":823,"refX":313,"refY":824,"markerWidth":825,"markerHeight":825,"orient":826},"coManualArrow","0 0 10 10","5","7","auto-start-reverse",[828,829],"path",{"d":830,"fill":87},"M0 0 L10 5 L0 10 z",[52,832,833],{"x":465,"y":55,"style":56,"fill":57,"textAnchor":58},"Moved labels get lines, others do not",[44,835],{"x":469,"y":836,"width":837,"height":838,"rx":313,"fill":472,"stroke":473,"style":474},"60","222","120",[52,840,843],{"x":841,"y":842,"style":479,"fill":57,"textAnchor":58},"135","97.78","fields",[52,845,847],{"x":841,"y":846,"style":484,"fill":473,"textAnchor":58},"123.78","label_x, label_y",[52,849,851],{"x":841,"y":850,"style":484,"fill":473,"textAnchor":58},"149.78","NULL = automatic",[83,853],{"x1":854,"y1":838,"x2":855,"y2":838,"stroke":87,"style":856},"246","288","stroke-width:1.8;marker-end:url(#coManualArrow)",[44,858],{"x":859,"y":836,"width":81,"height":838,"rx":313,"fill":493,"stroke":494,"style":474},"292",[52,861,864],{"x":862,"y":863,"style":479,"fill":494,"textAnchor":58},"392","110.78","data-defined",[52,866,868],{"x":862,"y":867,"style":484,"fill":473,"textAnchor":58},"136.78","position X\u002FY",[83,870],{"x1":871,"y1":838,"x2":872,"y2":838,"stroke":87,"style":856},"492","534",[44,874],{"x":875,"y":836,"width":115,"height":838,"rx":313,"fill":876,"stroke":877,"style":474},"538","#e8efe6","#15803d",[52,879,882],{"x":880,"y":863,"style":479,"fill":881,"textAnchor":58},"637","#166534","callouts",[52,884,885],{"x":880,"y":867,"style":484,"fill":473,"textAnchor":58},"only where moved",[144,887,889],{"className":146,"code":888,"language":148,"meta":149,"style":149},"from qgis.core import QgsField, QgsProperty, QgsPropertyCollection\nfrom qgis.PyQt.QtCore import QVariant\n\nprov = wells.dataProvider()\nfor name in (\"label_x\", \"label_y\"):\n    if wells.fields().indexOf(name) \u003C 0:\n        prov.addAttributes([QgsField(name, QVariant.Double)])\nwells.updateFields()\n\nprops = settings.dataDefinedProperties()\nprops.setProperty(QgsPalLayerSettings.PositionX, QgsProperty.fromField(\"label_x\"))\nprops.setProperty(QgsPalLayerSettings.PositionY, QgsProperty.fromField(\"label_y\"))\nsettings.setDataDefinedProperties(props)\nsettings.setCallout(callout)\nwells.setLabeling(QgsVectorLayerSimpleLabeling(settings))\nwells.triggerRepaint()\n",[151,890,891,902,914,918,928,953,970,975,980,984,994,1003,1012,1017,1021,1025],{"__ignoreMap":149},[154,892,893,895,897,899],{"class":83,"line":156},[154,894,160],{"class":159},[154,896,164],{"class":163},[154,898,167],{"class":159},[154,900,901],{"class":163}," QgsField, QgsProperty, QgsPropertyCollection\n",[154,903,904,906,909,911],{"class":83,"line":173},[154,905,160],{"class":159},[154,907,908],{"class":163}," qgis.PyQt.QtCore ",[154,910,167],{"class":159},[154,912,913],{"class":163}," QVariant\n",[154,915,916],{"class":83,"line":179},[154,917,196],{"emptyLinePlaceholder":195},[154,919,920,923,925],{"class":83,"line":192},[154,921,922],{"class":163},"prov ",[154,924,205],{"class":159},[154,926,927],{"class":163}," wells.dataProvider()\n",[154,929,930,933,936,939,942,945,947,950],{"class":83,"line":199},[154,931,932],{"class":159},"for",[154,934,935],{"class":163}," name ",[154,937,938],{"class":159},"in",[154,940,941],{"class":163}," (",[154,943,944],{"class":211},"\"label_x\"",[154,946,367],{"class":163},[154,948,949],{"class":211},"\"label_y\"",[154,951,952],{"class":163},"):\n",[154,954,955,958,961,964,967],{"class":83,"line":224},[154,956,957],{"class":159},"    if",[154,959,960],{"class":163}," wells.fields().indexOf(name) ",[154,962,963],{"class":159},"\u003C",[154,965,966],{"class":218}," 0",[154,968,969],{"class":163},":\n",[154,971,972],{"class":83,"line":229},[154,973,974],{"class":163},"        prov.addAttributes([QgsField(name, QVariant.Double)])\n",[154,976,977],{"class":83,"line":240},[154,978,979],{"class":163},"wells.updateFields()\n",[154,981,982],{"class":83,"line":251},[154,983,196],{"emptyLinePlaceholder":195},[154,985,986,989,991],{"class":83,"line":262},[154,987,988],{"class":163},"props ",[154,990,205],{"class":159},[154,992,993],{"class":163}," settings.dataDefinedProperties()\n",[154,995,996,999,1001],{"class":83,"line":273},[154,997,998],{"class":163},"props.setProperty(QgsPalLayerSettings.PositionX, QgsProperty.fromField(",[154,1000,944],{"class":211},[154,1002,304],{"class":163},[154,1004,1005,1008,1010],{"class":83,"line":284},[154,1006,1007],{"class":163},"props.setProperty(QgsPalLayerSettings.PositionY, QgsProperty.fromField(",[154,1009,949],{"class":211},[154,1011,304],{"class":163},[154,1013,1014],{"class":83,"line":295},[154,1015,1016],{"class":163},"settings.setDataDefinedProperties(props)\n",[154,1018,1019],{"class":83,"line":307},[154,1020,401],{"class":163},[154,1022,1023],{"class":83,"line":319},[154,1024,412],{"class":163},[154,1026,1027],{"class":83,"line":325},[154,1028,428],{"class":163},[14,1030,1031,1033],{},[432,1032,434],{}," With X and Y position properties bound to fields, QGIS's label move tool writes positions into those fields when a cartographer drags a label, and the labels stay put in every future render and export. Features whose fields are NULL keep automatic placement. Because the moved labels are away from their anchors, callouts draw for them; automatic labels close to their features stay clean thanks to the minimum length. Storing positions in a GeoPackage table alongside the data means they survive project changes; storing them in auxiliary storage keeps them inside the project instead.",[122,1035,1037],{"id":1036},"callouts-for-polygons-and-lines","Callouts for polygons and lines",[14,1039,1040],{},"Callouts are not only for points. Small polygons — building plots, ponds, protected sites — are often too small for their label, and lines such as pipes or cables carry labels best placed beside them in a tidy column.",[144,1042,1044],{"className":146,"code":1043,"language":148,"meta":149,"style":149},"plots = QgsProject.instance().mapLayersByName(\"allotment_plots\")[0]\nps = QgsPalLayerSettings()\nps.fieldName = \"plot_no\"\nps.placement = QgsPalLayerSettings.OverPoint\nps.fitInPolygonOnly = False\nps.setFormat(fmt)\n\nplot_callout = QgsSimpleLineCallout()\nplot_callout.setEnabled(True)\nplot_callout.setAnchorPoint(QgsCallout.PointOnSurface)\nplot_callout.setLineSymbol(QgsLineSymbol.createSimple({\"color\": \"#2f3b35\", \"width\": \"0.15\"}))\nplot_callout.setMinimumLength(1)\nps.setCallout(plot_callout)\n\nprops = ps.dataDefinedProperties()\nprops.setProperty(QgsPalLayerSettings.PositionX, QgsProperty.fromField(\"label_x\"))\nprops.setProperty(QgsPalLayerSettings.PositionY, QgsProperty.fromField(\"label_y\"))\nps.setDataDefinedProperties(props)\nplots.setLabeling(QgsVectorLayerSimpleLabeling(ps))\nplots.setLabelsEnabled(True)\n",[151,1045,1046,1064,1073,1083,1093,1103,1108,1112,1121,1130,1135,1157,1167,1172,1176,1185,1193,1201,1206,1211],{"__ignoreMap":149},[154,1047,1048,1051,1053,1055,1058,1060,1062],{"class":83,"line":156},[154,1049,1050],{"class":163},"plots ",[154,1052,205],{"class":159},[154,1054,208],{"class":163},[154,1056,1057],{"class":211},"\"allotment_plots\"",[154,1059,215],{"class":163},[154,1061,47],{"class":218},[154,1063,221],{"class":163},[154,1065,1066,1069,1071],{"class":83,"line":173},[154,1067,1068],{"class":163},"ps ",[154,1070,205],{"class":159},[154,1072,237],{"class":163},[154,1074,1075,1078,1080],{"class":83,"line":179},[154,1076,1077],{"class":163},"ps.fieldName ",[154,1079,205],{"class":159},[154,1081,1082],{"class":211}," \"plot_no\"\n",[154,1084,1085,1088,1090],{"class":83,"line":192},[154,1086,1087],{"class":163},"ps.placement ",[154,1089,205],{"class":159},[154,1091,1092],{"class":163}," QgsPalLayerSettings.OverPoint\n",[154,1094,1095,1098,1100],{"class":83,"line":199},[154,1096,1097],{"class":163},"ps.fitInPolygonOnly ",[154,1099,205],{"class":159},[154,1101,1102],{"class":218}," False\n",[154,1104,1105],{"class":83,"line":224},[154,1106,1107],{"class":163},"ps.setFormat(fmt)\n",[154,1109,1110],{"class":83,"line":229},[154,1111,196],{"emptyLinePlaceholder":195},[154,1113,1114,1117,1119],{"class":83,"line":240},[154,1115,1116],{"class":163},"plot_callout ",[154,1118,205],{"class":159},[154,1120,338],{"class":163},[154,1122,1123,1126,1128],{"class":83,"line":251},[154,1124,1125],{"class":163},"plot_callout.setEnabled(",[154,1127,347],{"class":218},[154,1129,316],{"class":163},[154,1131,1132],{"class":83,"line":262},[154,1133,1134],{"class":163},"plot_callout.setAnchorPoint(QgsCallout.PointOnSurface)\n",[154,1136,1137,1140,1142,1144,1146,1148,1150,1152,1155],{"class":83,"line":273},[154,1138,1139],{"class":163},"plot_callout.setLineSymbol(QgsLineSymbol.createSimple({",[154,1141,358],{"class":211},[154,1143,361],{"class":163},[154,1145,364],{"class":211},[154,1147,367],{"class":163},[154,1149,370],{"class":211},[154,1151,361],{"class":163},[154,1153,1154],{"class":211},"\"0.15\"",[154,1156,378],{"class":163},[154,1158,1159,1162,1165],{"class":83,"line":284},[154,1160,1161],{"class":163},"plot_callout.setMinimumLength(",[154,1163,1164],{"class":218},"1",[154,1166,316],{"class":163},[154,1168,1169],{"class":83,"line":295},[154,1170,1171],{"class":163},"ps.setCallout(plot_callout)\n",[154,1173,1174],{"class":83,"line":307},[154,1175,196],{"emptyLinePlaceholder":195},[154,1177,1178,1180,1182],{"class":83,"line":319},[154,1179,988],{"class":163},[154,1181,205],{"class":159},[154,1183,1184],{"class":163}," ps.dataDefinedProperties()\n",[154,1186,1187,1189,1191],{"class":83,"line":325},[154,1188,998],{"class":163},[154,1190,944],{"class":211},[154,1192,304],{"class":163},[154,1194,1195,1197,1199],{"class":83,"line":330},[154,1196,1007],{"class":163},[154,1198,949],{"class":211},[154,1200,304],{"class":163},[154,1202,1203],{"class":83,"line":341},[154,1204,1205],{"class":163},"ps.setDataDefinedProperties(props)\n",[154,1207,1208],{"class":83,"line":352},[154,1209,1210],{"class":163},"plots.setLabeling(QgsVectorLayerSimpleLabeling(ps))\n",[154,1212,1213,1216,1218],{"class":83,"line":381},[154,1214,1215],{"class":163},"plots.setLabelsEnabled(",[154,1217,347],{"class":218},[154,1219,316],{"class":163},[14,1221,1222,1224],{},[432,1223,434],{}," For polygons, anchoring at a point on the surface guarantees the line ends inside the plot, even for L-shaped or narrow plots whose centroid lies outside. Small plots get their labels moved into open space by hand, with positions stored in fields as above, and callouts link them back. For line layers, the anchor is the point on the line nearest the label, so a column of labels beside a bundle of cables connects each label to its own cable. In both cases the minimum length keeps callouts off labels that already sit on their feature.",[122,1226,1228],{"id":1227},"check-callouts-at-the-export-scale","Check callouts at the export scale",[14,1230,1231],{},"Callouts are sized in millimetres, and labels are placed for the scale being rendered — so callouts that look right on screen can overlap or vanish in a layout at a different scale. Export the layout, or a sample page of an atlas, and look at it at full size before finalising: check that lines do not cross each other or other labels, that none are so short they look like stray marks, and that manually placed labels still sit where intended at the print scale. Manual positions are stored in map units, so they are scale-independent, but the space around them is not.",[122,1233,1235],{"id":1234},"style-leader-lines-well","Style leader lines well",[14,1237,1238],{},"A leader line should be obvious enough to follow and quiet enough to ignore. A few habits help.",[144,1240,1242],{"className":146,"code":1241,"language":148,"meta":149,"style":149},"leader = QgsLineSymbol.createSimple({\n    \"color\": \"#2f3b35\", \"width\": \"0.18\", \"capstyle\": \"round\"})\ncallout.setLineSymbol(leader)\ncallout.setMinimumLength(1.5)\ncallout.setDrawCalloutToAllParts(False)\nsettings.setCallout(callout)\nwells.setLabeling(QgsVectorLayerSimpleLabeling(settings))\n",[151,1243,1244,1254,1285,1290,1299,1307,1311],{"__ignoreMap":149},[154,1245,1246,1249,1251],{"class":83,"line":156},[154,1247,1248],{"class":163},"leader ",[154,1250,205],{"class":159},[154,1252,1253],{"class":163}," QgsLineSymbol.createSimple({\n",[154,1255,1256,1259,1261,1263,1265,1267,1269,1272,1274,1277,1279,1282],{"class":83,"line":173},[154,1257,1258],{"class":211},"    \"color\"",[154,1260,361],{"class":163},[154,1262,364],{"class":211},[154,1264,367],{"class":163},[154,1266,370],{"class":211},[154,1268,361],{"class":163},[154,1270,1271],{"class":211},"\"0.18\"",[154,1273,367],{"class":163},[154,1275,1276],{"class":211},"\"capstyle\"",[154,1278,361],{"class":163},[154,1280,1281],{"class":211},"\"round\"",[154,1283,1284],{"class":163},"})\n",[154,1286,1287],{"class":83,"line":179},[154,1288,1289],{"class":163},"callout.setLineSymbol(leader)\n",[154,1291,1292,1294,1297],{"class":83,"line":192},[154,1293,384],{"class":163},[154,1295,1296],{"class":218},"1.5",[154,1298,316],{"class":163},[154,1300,1301,1303,1305],{"class":83,"line":199},[154,1302,779],{"class":163},[154,1304,782],{"class":218},[154,1306,316],{"class":163},[154,1308,1309],{"class":83,"line":224},[154,1310,401],{"class":163},[154,1312,1313],{"class":83,"line":229},[154,1314,412],{"class":163},[14,1316,1317,1319,1320,1324],{},[432,1318,434],{}," Thin (around 0.15–0.25 mm), dark and slightly lighter than label text works on most basemaps. Avoid colours used for data, so leader lines are not read as features — a blue line on a hydrology map looks like a stream. Lines should not cross each other: if they do, reorder labels manually or switch to Manhattan callouts, which cross less in column layouts. A small marker at the feature end can help on busy maps; build it as a marker line symbol layer with a marker at the last vertex, as described in ",[21,1321,1323],{"href":1322},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fmarker-line-and-interval-symbols-pyqgis\u002F","marker line and interval symbols",".",[122,1326,1328],{"id":1327},"qgis-version-compatibility","QGIS version compatibility",[14,1330,1331,367,1334,1337,1338,1341,1342,1345,1346,367,1349,1352,1353,1324],{},[151,1332,1333],{},"QgsSimpleLineCallout",[151,1335,1336],{},"QgsManhattanLineCallout"," and the anchor settings are available from QGIS 3.10; ",[151,1339,1340],{},"QgsCurvedLineCallout"," and ",[151,1343,1344],{},"QgsBalloonCallout"," from the 3.20 series; all are present on 3.34 LTR, 3.40 LTR and QGIS 4. On QGIS 4, enums are scoped — ",[151,1347,1348],{},"QgsCallout.AnchorPoint.PoleOfInaccessibility",[151,1350,1351],{},"Qgis.LabelPlacement.AroundPoint"," — and units use ",[151,1354,1355],{},"Qgis.RenderUnit.Millimeters",[122,1357,1359],{"id":1358},"troubleshooting","Troubleshooting",[127,1361,1362,1368,1374,1380],{},[130,1363,1364,1367],{},[432,1365,1366],{},"No callouts appear."," Labels sit at their anchors, the callout is disabled, or the minimum length is larger than the displacement.",[130,1369,1370,1373],{},[432,1371,1372],{},"Callouts point to the wrong place on polygons."," The anchor is the centroid; use the pole of inaccessibility or point on surface.",[130,1375,1376,1379],{},[432,1377,1378],{},"Lines cross labels or each other."," Move labels manually or try Manhattan callouts.",[130,1381,1382,1385],{},[432,1383,1384],{},"Moved labels jump back."," Positions are not stored; bind X and Y to fields or auxiliary storage.",[122,1387,1389],{"id":1388},"conclusion","Conclusion",[14,1391,1392],{},"Enable a callout in the label settings with a thin dark line and a minimum length, pick simple, Manhattan, curved or balloon style to suit the map, anchor polygon callouts at the pole of inaccessibility, store manual label positions in fields so callouts connect moved labels reproducibly, and keep leader lines quiet and uncrossed.",[122,1394,1396],{"id":1395},"frequently-asked-questions","Frequently Asked Questions",[14,1398,1399,1402],{},[432,1400,1401],{},"Do callouts work with rule-based labelling?","\nYes — each rule's settings can have its own callout.",[14,1404,1405,1408],{},[432,1406,1407],{},"Can callouts be data-defined?","\nMany properties can, including whether the callout is drawn and its anchor.",[14,1410,1411,1414],{},[432,1412,1413],{},"Are callouts included in SVG exports?","\nYes, as vector lines.",[14,1416,1417,1420],{},[432,1418,1419],{},"Can I draw callouts for diagrams?","\nNo — callouts belong to labels. Offset diagrams use their own placement.",[122,1422,1424],{"id":1423},"related","Related",[127,1426,1427,1432,1438,1444,1450],{},[130,1428,1429,1431],{},[21,1430,24],{"href":23}," — the guide this recipe belongs to",[130,1433,1434],{},[21,1435,1437],{"href":1436},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fcontrol-label-placement-and-collisions-pyqgis\u002F","Control Label Placement and Collisions in PyQGIS",[130,1439,1440],{},[21,1441,1443],{"href":1442},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fuse-html-formatted-labels-pyqgis\u002F","Use HTML-Formatted Labels in PyQGIS",[130,1445,1446],{},[21,1447,1449],{"href":1448},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-rule-based-labels-pyqgis\u002F","Add Rule-Based Labels in PyQGIS",[130,1451,1452],{},[21,1453,1455],{"href":1454},"\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-text-annotation-to-map-pyqgis\u002F","Add a Text Annotation to a Map in PyQGIS",[1457,1458,1459],"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":149,"searchDepth":173,"depth":173,"links":1461},[1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474],{"id":124,"depth":173,"text":125},{"id":138,"depth":173,"text":139},{"id":442,"depth":173,"text":443},{"id":721,"depth":173,"text":722},{"id":796,"depth":173,"text":797},{"id":1036,"depth":173,"text":1037},{"id":1227,"depth":173,"text":1228},{"id":1234,"depth":173,"text":1235},{"id":1327,"depth":173,"text":1328},{"id":1358,"depth":173,"text":1359},{"id":1388,"depth":173,"text":1389},{"id":1395,"depth":173,"text":1396},{"id":1423,"depth":173,"text":1424},"Connect labels to their features with leader lines when labels are moved away — simple, Manhattan, curved and balloon callouts, anchor and label attachment points, minimum length, and storing manual label positions so callouts appear only where labels were displaced.","md",{"slug":1478,"type":1479,"breadcrumb":1480,"datePublished":1481,"dateModified":1481},"add-label-callouts-pyqgis","article","Add Label Callouts","2026-10-02","\u002Fpyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-label-callouts-pyqgis",{"title":5,"description":1475},"pyqgis-cartography-visualization\u002Flabeling-and-annotations\u002Fadd-label-callouts-pyqgis\u002Findex","cbMuEj5isv4jKhwjQYrdeVt-ANtS_be_SICjJ_mBwFY",1790966252903]