[{"data":1,"prerenderedAt":1674},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Farrow-symbol-layer-pyqgis":3},{"id":4,"title":5,"body":6,"description":1663,"extension":1664,"meta":1665,"navigation":178,"path":1670,"seo":1671,"stem":1672,"__hash__":1673},"docs\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Farrow-symbol-layer-pyqgis\u002Findex.md","Use the Arrow Symbol Layer in PyQGIS",{"type":7,"value":8,"toc":1649},"minimark",[9,13,17,26,113,118,133,137,140,382,413,417,420,483,582,592,596,599,757,762,766,769,1150,1160,1164,1167,1211,1431,1445,1449,1452,1506,1511,1515,1531,1535,1564,1568,1574,1578,1584,1594,1600,1606,1612,1616,1645],[10,11,5],"h1",{"id":12},"use-the-arrow-symbol-layer-in-pyqgis",[14,15,16],"p",{},"Arrows communicate movement: commuters from suburbs to centre, goods between ports, river flow, the direction of one-way streets, the path of a storm. QGIS's arrow symbol layer turns any line into a filled arrow with a shaft and head whose sizes, curvature and fill can be set — and data-defined, which is what makes proportional flow maps possible. Because it is a symbol layer, it stacks with others and works in every renderer.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002F","Symbol Layers & Advanced Symbology",". It creates arrows on lines, adjusts head and shaft geometry, curves arrows between points, scales width by a flow value, styles the arrow fill, and covers direction-only arrows for networks.",[14,27,28],{},[29,30,35,39,43,50,59,66,74,79,84,89,96,101,106],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 234","img","An arrow symbol with a shaft whose start and end widths can differ and a head with configurable length and thickness","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Parts of an arrow symbol",[40,41,42],"desc",{},"An arrow symbol drawn along a line has a shaft with a start width and an end width, and a head with a length and a thickness. The head can be at the end, at the start, or at both ends. Setting different start and end widths makes a tapered arrow. The shaft is filled with any fill symbol, including gradients.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","234","#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","Shaft widths, head length, head thickness",[60,61],"polygon",{"points":62,"fill":63,"stroke":64,"style":65},"80,130 520,118 520,96 640,140 520,184 520,162 80,150","#eef7f4","#0f766e","stroke-width:2",[67,68],"line",{"x1":69,"y1":70,"x2":69,"y2":71,"stroke":72,"style":73},"80","110","170","#59645f","stroke-width:1;stroke-dasharray:3 3",[51,75,78],{"x":69,"y":76,"style":77,"fill":72,"textAnchor":57},"196","text-anchor:middle;font-size:10.5px;font-family:sans-serif","start width",[67,80],{"x1":81,"y1":82,"x2":81,"y2":83,"stroke":72,"style":73},"500","118","162",[51,85,88],{"x":86,"y":87,"style":77,"fill":72,"textAnchor":57},"470","214","end width",[67,90],{"x1":91,"y1":92,"x2":93,"y2":92,"stroke":94,"style":95},"520","70","640","#b45309","stroke-width:1.5",[51,97,100],{"x":98,"y":99,"style":77,"fill":94,"textAnchor":57},"580","60","head length",[67,102],{"x1":103,"y1":104,"x2":103,"y2":105,"stroke":94,"style":95},"660","96","184",[51,107,112],{"x":108,"y":109,"style":110,"fill":94,"textAnchor":111},"668","144","text-anchor:start;font-size:10.0px;font-family:sans-serif","start","head thickness",[114,115,117],"h2",{"id":116},"prerequisites","Prerequisites",[119,120,121,125],"ul",{},[122,123,124],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[122,126,127,128,132],{},"A line layer — flows between places, river centrelines, streets with a direction field. For flows between point pairs, lines can be built with ",[129,130,131],"code",{},"native:pointstopath"," or a virtual layer joining origins and destinations.",[114,134,136],{"id":135},"add-an-arrow-symbol-layer","Add an arrow symbol layer",[14,138,139],{},"The arrow layer replaces or sits on top of a line's usual symbol layer. Widths and head sizes default to millimetres.",[141,142,147],"pre",{"className":143,"code":144,"language":145,"meta":146,"style":146},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsProject, QgsLineSymbol, QgsArrowSymbolLayer, QgsFillSymbol,\n                       QgsSingleSymbolRenderer, QgsUnitTypes)\n\nflows = QgsProject.instance().mapLayersByName(\"commuter_flows\")[0]\n\narrow = QgsArrowSymbolLayer()\narrow.setArrowWidth(1.2)\narrow.setArrowStartWidth(1.2)\narrow.setHeadLength(3.0)\narrow.setHeadThickness(1.8)\narrow.setHeadType(QgsArrowSymbolLayer.HeadSingle)\narrow.setArrowType(QgsArrowSymbolLayer.ArrowPlain)\narrow.setIsCurved(False)\narrow.setIsRepeated(False)\narrow.setSubSymbol(QgsFillSymbol.createSimple(\n    {\"color\": \"#2563eb\", \"outline_color\": \"#17211d\", \"outline_width\": \"0.15\"}))\n\nsymbol = QgsLineSymbol()\nsymbol.changeSymbolLayer(0, arrow)\nflows.setRenderer(QgsSingleSymbolRenderer(symbol))\nflows.triggerRepaint()\n","python","",[129,148,149,167,173,180,205,210,221,233,243,254,265,271,277,288,298,304,343,348,359,370,376],{"__ignoreMap":146},[150,151,153,157,161,164],"span",{"class":67,"line":152},1,[150,154,156],{"class":155},"snl16","from",[150,158,160],{"class":159},"s95oV"," qgis.core ",[150,162,163],{"class":155},"import",[150,165,166],{"class":159}," (QgsProject, QgsLineSymbol, QgsArrowSymbolLayer, QgsFillSymbol,\n",[150,168,170],{"class":67,"line":169},2,[150,171,172],{"class":159},"                       QgsSingleSymbolRenderer, QgsUnitTypes)\n",[150,174,176],{"class":67,"line":175},3,[150,177,179],{"emptyLinePlaceholder":178},true,"\n",[150,181,183,186,189,192,196,199,202],{"class":67,"line":182},4,[150,184,185],{"class":159},"flows ",[150,187,188],{"class":155},"=",[150,190,191],{"class":159}," QgsProject.instance().mapLayersByName(",[150,193,195],{"class":194},"sU2Wk","\"commuter_flows\"",[150,197,198],{"class":159},")[",[150,200,46],{"class":201},"sDLfK",[150,203,204],{"class":159},"]\n",[150,206,208],{"class":67,"line":207},5,[150,209,179],{"emptyLinePlaceholder":178},[150,211,213,216,218],{"class":67,"line":212},6,[150,214,215],{"class":159},"arrow ",[150,217,188],{"class":155},[150,219,220],{"class":159}," QgsArrowSymbolLayer()\n",[150,222,224,227,230],{"class":67,"line":223},7,[150,225,226],{"class":159},"arrow.setArrowWidth(",[150,228,229],{"class":201},"1.2",[150,231,232],{"class":159},")\n",[150,234,236,239,241],{"class":67,"line":235},8,[150,237,238],{"class":159},"arrow.setArrowStartWidth(",[150,240,229],{"class":201},[150,242,232],{"class":159},[150,244,246,249,252],{"class":67,"line":245},9,[150,247,248],{"class":159},"arrow.setHeadLength(",[150,250,251],{"class":201},"3.0",[150,253,232],{"class":159},[150,255,257,260,263],{"class":67,"line":256},10,[150,258,259],{"class":159},"arrow.setHeadThickness(",[150,261,262],{"class":201},"1.8",[150,264,232],{"class":159},[150,266,268],{"class":67,"line":267},11,[150,269,270],{"class":159},"arrow.setHeadType(QgsArrowSymbolLayer.HeadSingle)\n",[150,272,274],{"class":67,"line":273},12,[150,275,276],{"class":159},"arrow.setArrowType(QgsArrowSymbolLayer.ArrowPlain)\n",[150,278,280,283,286],{"class":67,"line":279},13,[150,281,282],{"class":159},"arrow.setIsCurved(",[150,284,285],{"class":201},"False",[150,287,232],{"class":159},[150,289,291,294,296],{"class":67,"line":290},14,[150,292,293],{"class":159},"arrow.setIsRepeated(",[150,295,285],{"class":201},[150,297,232],{"class":159},[150,299,301],{"class":67,"line":300},15,[150,302,303],{"class":159},"arrow.setSubSymbol(QgsFillSymbol.createSimple(\n",[150,305,307,310,313,316,319,322,325,327,330,332,335,337,340],{"class":67,"line":306},16,[150,308,309],{"class":159},"    {",[150,311,312],{"class":194},"\"color\"",[150,314,315],{"class":159},": ",[150,317,318],{"class":194},"\"#2563eb\"",[150,320,321],{"class":159},", ",[150,323,324],{"class":194},"\"outline_color\"",[150,326,315],{"class":159},[150,328,329],{"class":194},"\"#17211d\"",[150,331,321],{"class":159},[150,333,334],{"class":194},"\"outline_width\"",[150,336,315],{"class":159},[150,338,339],{"class":194},"\"0.15\"",[150,341,342],{"class":159},"}))\n",[150,344,346],{"class":67,"line":345},17,[150,347,179],{"emptyLinePlaceholder":178},[150,349,351,354,356],{"class":67,"line":350},18,[150,352,353],{"class":159},"symbol ",[150,355,188],{"class":155},[150,357,358],{"class":159}," QgsLineSymbol()\n",[150,360,362,365,367],{"class":67,"line":361},19,[150,363,364],{"class":159},"symbol.changeSymbolLayer(",[150,366,46],{"class":201},[150,368,369],{"class":159},", arrow)\n",[150,371,373],{"class":67,"line":372},20,[150,374,375],{"class":159},"flows.setRenderer(QgsSingleSymbolRenderer(symbol))\n",[150,377,379],{"class":67,"line":378},21,[150,380,381],{"class":159},"flows.triggerRepaint()\n",[14,383,384,388,389,392,393,396,397,400,401,404,405,408,409,412],{},[385,386,387],"strong",{},"Breakdown:"," ",[129,390,391],{},"setArrowWidth"," is the shaft width at the end and ",[129,394,395],{},"setArrowStartWidth"," at the start; equal values give a parallel shaft. Head length and thickness are measured along and across the line. ",[129,398,399],{},"HeadSingle"," puts the head at the end of the line — the direction of digitizing — and ",[129,402,403],{},"HeadReversed"," or ",[129,406,407],{},"HeadDouble"," change that. The fill is a sub-symbol, so any fill style works, including gradients and outlines. ",[129,410,411],{},"setIsRepeated(False)"," draws one arrow per feature rather than one per line segment.",[114,414,416],{"id":415},"curve-arrows-between-two-points","Curve arrows between two points",[14,418,419],{},"Straight arrows between many origins and destinations overlap along the same corridor. Curving them separates opposite directions and makes flow maps readable.",[14,421,422],{},[29,423,426,429,432,435,438,445,448,455,458,463,466,469,474,477],{"viewBox":424,"role":32,"ariaLabel":425,"xmlns":34},"0 0 760 230","Two opposing straight arrows overlapping, compared with curved arrows that bend apart into two visible arcs",[36,427,428],{},"Curved arrows separate directions",[40,430,431],{},"Two straight arrows between the same pair of places, one in each direction, lie on top of each other. Curved arrows bend to the side, so the outbound and return flows separate into two visible arcs, each with its own width. Curvature is applied to lines with three or more vertices or generated from a two-point line.",[44,433],{"x":46,"y":46,"width":47,"height":434,"fill":49},"230",[51,436,437],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Opposing flows on separate arcs",[439,440],"circle",{"cx":441,"cy":442,"r":443,"fill":56,"stroke":56,"style":444},"120","140","7","stroke-width:1",[439,446],{"cx":447,"cy":442,"r":443,"fill":56,"stroke":56,"style":444},"330",[67,449],{"x1":450,"y1":451,"x2":452,"y2":451,"stroke":453,"style":454},"130","138","320","#2563eb","stroke-width:4",[67,456],{"x1":452,"y1":457,"x2":450,"y2":457,"stroke":94,"style":65},"142",[51,459,462],{"x":460,"y":461,"style":77,"fill":72,"textAnchor":57},"225","190","straight: overlap",[439,464],{"cx":465,"cy":442,"r":443,"fill":56,"stroke":56,"style":444},"440",[439,467],{"cx":468,"cy":442,"r":443,"fill":56,"stroke":56,"style":444},"650",[470,471],"path",{"d":472,"fill":473,"stroke":453,"style":454},"M 448 132 Q 545 70 642 132","none",[470,475],{"d":476,"fill":473,"stroke":94,"style":65},"M 642 148 Q 545 210 448 148",[51,478,482],{"x":479,"y":480,"style":77,"fill":481,"textAnchor":57},"545","222","#166534","curved: separate",[141,484,486],{"className":143,"code":485,"language":145,"meta":146,"style":146},"arrow.setIsCurved(True)\nsymbol = QgsLineSymbol()\nsymbol.changeSymbolLayer(0, arrow)\nflows.setRenderer(QgsSingleSymbolRenderer(symbol))\n\n# a two-point flow line needs a midpoint to curve; build one offset to the right\nfrom qgis.core import QgsProperty, QgsSymbolLayer\ngen = (\"make_line(start_point($geometry), \"\n       \"project(centroid($geometry), length($geometry) * 0.15, \"\n       \"azimuth(start_point($geometry), end_point($geometry)) + pi()\u002F2), \"\n       \"end_point($geometry))\")\nprint(\"geometry generator for curved flows:\", gen)\n",[129,487,488,497,505,513,517,521,527,538,551,556,561,568],{"__ignoreMap":146},[150,489,490,492,495],{"class":67,"line":152},[150,491,282],{"class":159},[150,493,494],{"class":201},"True",[150,496,232],{"class":159},[150,498,499,501,503],{"class":67,"line":169},[150,500,353],{"class":159},[150,502,188],{"class":155},[150,504,358],{"class":159},[150,506,507,509,511],{"class":67,"line":175},[150,508,364],{"class":159},[150,510,46],{"class":201},[150,512,369],{"class":159},[150,514,515],{"class":67,"line":182},[150,516,375],{"class":159},[150,518,519],{"class":67,"line":207},[150,520,179],{"emptyLinePlaceholder":178},[150,522,523],{"class":67,"line":212},[150,524,526],{"class":525},"sjoCn","# a two-point flow line needs a midpoint to curve; build one offset to the right\n",[150,528,529,531,533,535],{"class":67,"line":223},[150,530,156],{"class":155},[150,532,160],{"class":159},[150,534,163],{"class":155},[150,536,537],{"class":159}," QgsProperty, QgsSymbolLayer\n",[150,539,540,543,545,548],{"class":67,"line":235},[150,541,542],{"class":159},"gen ",[150,544,188],{"class":155},[150,546,547],{"class":159}," (",[150,549,550],{"class":194},"\"make_line(start_point($geometry), \"\n",[150,552,553],{"class":67,"line":245},[150,554,555],{"class":194},"       \"project(centroid($geometry), length($geometry) * 0.15, \"\n",[150,557,558],{"class":67,"line":256},[150,559,560],{"class":194},"       \"azimuth(start_point($geometry), end_point($geometry)) + pi()\u002F2), \"\n",[150,562,563,566],{"class":67,"line":267},[150,564,565],{"class":194},"       \"end_point($geometry))\"",[150,567,232],{"class":159},[150,569,570,573,576,579],{"class":67,"line":273},[150,571,572],{"class":201},"print",[150,574,575],{"class":159},"(",[150,577,578],{"class":194},"\"geometry generator for curved flows:\"",[150,580,581],{"class":159},", gen)\n",[14,583,584,586,587,591],{},[385,585,387],{}," The arrow layer curves lines that have at least three vertices, following a smooth curve through them. Flow lines built from origin–destination pairs have only two, so a midpoint offset to the right of the line — here by 15 % of its length — gives the curve its bend. Because the offset is always to the right of the direction of travel, A→B and B→A bend to opposite sides and separate. Apply the expression in a geometry generator symbol layer wrapping the arrow, as in ",[21,588,590],{"href":589},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fgeometry-generator-symbol-layer-pyqgis\u002F","using a geometry generator symbol layer",", or materialise it once with the geometry by expression algorithm.",[114,593,595],{"id":594},"scale-width-by-flow-volume","Scale width by flow volume",[14,597,598],{},"A flow map's message is in the widths. Data-defined widths turn a volume field into shaft width, with area or width proportional to the value.",[141,600,602],{"className":143,"code":601,"language":145,"meta":146,"style":146},"max_flow = max(f[\"commuters\"] or 0 for f in flows.getFeatures())\nwidth_expr = f'0.3 + 4.0 * sqrt(coalesce(\"commuters\", 0) \u002F {max_flow})'\n\narrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowWidth, QgsProperty.fromExpression(width_expr))\narrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowStartWidth, QgsProperty.fromExpression(width_expr))\narrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowHeadThickness,\n                             QgsProperty.fromExpression(f\"1.6 * ({width_expr})\"))\narrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowHeadLength,\n                             QgsProperty.fromExpression(f\"2 + 2 * ({width_expr})\"))\nsymbol = QgsLineSymbol()\nsymbol.changeSymbolLayer(0, arrow)\nflows.setRenderer(QgsSingleSymbolRenderer(symbol))\nflows.triggerRepaint()\n",[129,603,604,641,666,670,675,680,685,709,714,733,741,749,753],{"__ignoreMap":146},[150,605,606,609,611,614,617,620,623,626,629,632,635,638],{"class":67,"line":152},[150,607,608],{"class":159},"max_flow ",[150,610,188],{"class":155},[150,612,613],{"class":201}," max",[150,615,616],{"class":159},"(f[",[150,618,619],{"class":194},"\"commuters\"",[150,621,622],{"class":159},"] ",[150,624,625],{"class":155},"or",[150,627,628],{"class":201}," 0",[150,630,631],{"class":155}," for",[150,633,634],{"class":159}," f ",[150,636,637],{"class":155},"in",[150,639,640],{"class":159}," flows.getFeatures())\n",[150,642,643,646,648,651,654,657,660,663],{"class":67,"line":169},[150,644,645],{"class":159},"width_expr ",[150,647,188],{"class":155},[150,649,650],{"class":155}," f",[150,652,653],{"class":194},"'0.3 + 4.0 * sqrt(coalesce(\"commuters\", 0) \u002F ",[150,655,656],{"class":201},"{",[150,658,659],{"class":159},"max_flow",[150,661,662],{"class":201},"}",[150,664,665],{"class":194},")'\n",[150,667,668],{"class":67,"line":175},[150,669,179],{"emptyLinePlaceholder":178},[150,671,672],{"class":67,"line":182},[150,673,674],{"class":159},"arrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowWidth, QgsProperty.fromExpression(width_expr))\n",[150,676,677],{"class":67,"line":207},[150,678,679],{"class":159},"arrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowStartWidth, QgsProperty.fromExpression(width_expr))\n",[150,681,682],{"class":67,"line":212},[150,683,684],{"class":159},"arrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowHeadThickness,\n",[150,686,687,690,693,696,698,701,703,706],{"class":67,"line":223},[150,688,689],{"class":159},"                             QgsProperty.fromExpression(",[150,691,692],{"class":155},"f",[150,694,695],{"class":194},"\"1.6 * (",[150,697,656],{"class":201},[150,699,700],{"class":159},"width_expr",[150,702,662],{"class":201},[150,704,705],{"class":194},")\"",[150,707,708],{"class":159},"))\n",[150,710,711],{"class":67,"line":235},[150,712,713],{"class":159},"arrow.setDataDefinedProperty(QgsSymbolLayer.PropertyArrowHeadLength,\n",[150,715,716,718,720,723,725,727,729,731],{"class":67,"line":245},[150,717,689],{"class":159},[150,719,692],{"class":155},[150,721,722],{"class":194},"\"2 + 2 * (",[150,724,656],{"class":201},[150,726,700],{"class":159},[150,728,662],{"class":201},[150,730,705],{"class":194},[150,732,708],{"class":159},[150,734,735,737,739],{"class":67,"line":256},[150,736,353],{"class":159},[150,738,188],{"class":155},[150,740,358],{"class":159},[150,742,743,745,747],{"class":67,"line":267},[150,744,364],{"class":159},[150,746,46],{"class":201},[150,748,369],{"class":159},[150,750,751],{"class":67,"line":273},[150,752,375],{"class":159},[150,754,755],{"class":67,"line":279},[150,756,381],{"class":159},[14,758,759,761],{},[385,760,387],{}," The width grows with the square root of the flow, normalised by the maximum, from 0.3 mm for tiny flows to about 4.3 mm for the largest; the square root keeps big flows from overwhelming the map. Head thickness and length follow the width, so heads stay in proportion on thick and thin arrows. Using the same expression for start and end width gives parallel shafts; a smaller start width tapers each arrow towards its origin. Filtering out flows below a threshold, with a rule-based renderer, removes the many tiny arrows that would otherwise clutter the map.",[114,763,765],{"id":764},"build-flow-lines-from-an-origindestination-table","Build flow lines from an origin–destination table",[14,767,768],{},"Flow data usually arrives as a table — origin, destination, volume — plus a layer of places. Turning it into lines is the first step of any flow map, and doing it in Python keeps the link between table rows and arrows explicit.",[141,770,772],{"className":143,"code":771,"language":145,"meta":146,"style":146},"import csv\nfrom qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY\n\nplaces = QgsProject.instance().mapLayersByName(\"municipalities\")[0]\ncentre = {f[\"code\"]: f.geometry().pointOnSurface().asPoint() for f in places.getFeatures()}\n\nod = QgsVectorLayer(f\"LineString?crs={places.crs().authid()}&field=origin:string\"\n                    \"&field=destination:string&field=commuters:integer\", \"commuter flows\", \"memory\")\nrows, missing = [], set()\nwith open(\"\u002Fdata\u002Fcensus\u002Fcommuting_od.csv\", newline=\"\", encoding=\"utf-8\") as fh:\n    for r in csv.DictReader(fh):\n        a, b = centre.get(r[\"origin\"]), centre.get(r[\"destination\"])\n        if a is None or b is None or r[\"origin\"] == r[\"destination\"]:\n            missing.update(c for c in (r[\"origin\"], r[\"destination\"]) if c not in centre)\n            continue\n        f = QgsFeature(od.fields())\n        f.setAttributes([r[\"origin\"], r[\"destination\"], int(r[\"commuters\"])])\n        f.setGeometry(QgsGeometry.fromPolylineXY([QgsPointXY(a), QgsPointXY(b)]))\n        rows.append(f)\nod.dataProvider().addFeatures(rows)\nQgsProject.instance().addMapLayer(od)\nprint(len(rows), \"flows;\", len(missing), \"unknown place codes\")\n",[129,773,774,781,792,796,814,840,844,869,886,902,945,958,980,1023,1062,1067,1077,1102,1107,1112,1117,1122],{"__ignoreMap":146},[150,775,776,778],{"class":67,"line":152},[150,777,163],{"class":155},[150,779,780],{"class":159}," csv\n",[150,782,783,785,787,789],{"class":67,"line":169},[150,784,156],{"class":155},[150,786,160],{"class":159},[150,788,163],{"class":155},[150,790,791],{"class":159}," QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY\n",[150,793,794],{"class":67,"line":175},[150,795,179],{"emptyLinePlaceholder":178},[150,797,798,801,803,805,808,810,812],{"class":67,"line":182},[150,799,800],{"class":159},"places ",[150,802,188],{"class":155},[150,804,191],{"class":159},[150,806,807],{"class":194},"\"municipalities\"",[150,809,198],{"class":159},[150,811,46],{"class":201},[150,813,204],{"class":159},[150,815,816,819,821,824,827,830,833,835,837],{"class":67,"line":207},[150,817,818],{"class":159},"centre ",[150,820,188],{"class":155},[150,822,823],{"class":159}," {f[",[150,825,826],{"class":194},"\"code\"",[150,828,829],{"class":159},"]: f.geometry().pointOnSurface().asPoint() ",[150,831,832],{"class":155},"for",[150,834,634],{"class":159},[150,836,637],{"class":155},[150,838,839],{"class":159}," places.getFeatures()}\n",[150,841,842],{"class":67,"line":212},[150,843,179],{"emptyLinePlaceholder":178},[150,845,846,849,851,854,856,859,861,864,866],{"class":67,"line":223},[150,847,848],{"class":159},"od ",[150,850,188],{"class":155},[150,852,853],{"class":159}," QgsVectorLayer(",[150,855,692],{"class":155},[150,857,858],{"class":194},"\"LineString?crs=",[150,860,656],{"class":201},[150,862,863],{"class":159},"places.crs().authid()",[150,865,662],{"class":201},[150,867,868],{"class":194},"&field=origin:string\"\n",[150,870,871,874,876,879,881,884],{"class":67,"line":235},[150,872,873],{"class":194},"                    \"&field=destination:string&field=commuters:integer\"",[150,875,321],{"class":159},[150,877,878],{"class":194},"\"commuter flows\"",[150,880,321],{"class":159},[150,882,883],{"class":194},"\"memory\"",[150,885,232],{"class":159},[150,887,888,891,893,896,899],{"class":67,"line":245},[150,889,890],{"class":159},"rows, missing ",[150,892,188],{"class":155},[150,894,895],{"class":159}," [], ",[150,897,898],{"class":201},"set",[150,900,901],{"class":159},"()\n",[150,903,904,907,910,912,915,917,921,923,926,928,931,933,936,939,942],{"class":67,"line":256},[150,905,906],{"class":155},"with",[150,908,909],{"class":201}," open",[150,911,575],{"class":159},[150,913,914],{"class":194},"\"\u002Fdata\u002Fcensus\u002Fcommuting_od.csv\"",[150,916,321],{"class":159},[150,918,920],{"class":919},"s9osk","newline",[150,922,188],{"class":155},[150,924,925],{"class":194},"\"\"",[150,927,321],{"class":159},[150,929,930],{"class":919},"encoding",[150,932,188],{"class":155},[150,934,935],{"class":194},"\"utf-8\"",[150,937,938],{"class":159},") ",[150,940,941],{"class":155},"as",[150,943,944],{"class":159}," fh:\n",[150,946,947,950,953,955],{"class":67,"line":267},[150,948,949],{"class":155},"    for",[150,951,952],{"class":159}," r ",[150,954,637],{"class":155},[150,956,957],{"class":159}," csv.DictReader(fh):\n",[150,959,960,963,965,968,971,974,977],{"class":67,"line":273},[150,961,962],{"class":159},"        a, b ",[150,964,188],{"class":155},[150,966,967],{"class":159}," centre.get(r[",[150,969,970],{"class":194},"\"origin\"",[150,972,973],{"class":159},"]), centre.get(r[",[150,975,976],{"class":194},"\"destination\"",[150,978,979],{"class":159},"])\n",[150,981,982,985,988,991,994,997,1000,1002,1004,1006,1009,1011,1013,1016,1018,1020],{"class":67,"line":279},[150,983,984],{"class":155},"        if",[150,986,987],{"class":159}," a ",[150,989,990],{"class":155},"is",[150,992,993],{"class":201}," None",[150,995,996],{"class":155}," or",[150,998,999],{"class":159}," b ",[150,1001,990],{"class":155},[150,1003,993],{"class":201},[150,1005,996],{"class":155},[150,1007,1008],{"class":159}," r[",[150,1010,970],{"class":194},[150,1012,622],{"class":159},[150,1014,1015],{"class":155},"==",[150,1017,1008],{"class":159},[150,1019,976],{"class":194},[150,1021,1022],{"class":159},"]:\n",[150,1024,1025,1028,1030,1033,1035,1038,1040,1043,1045,1048,1051,1053,1056,1059],{"class":67,"line":290},[150,1026,1027],{"class":159},"            missing.update(c ",[150,1029,832],{"class":155},[150,1031,1032],{"class":159}," c ",[150,1034,637],{"class":155},[150,1036,1037],{"class":159}," (r[",[150,1039,970],{"class":194},[150,1041,1042],{"class":159},"], r[",[150,1044,976],{"class":194},[150,1046,1047],{"class":159},"]) ",[150,1049,1050],{"class":155},"if",[150,1052,1032],{"class":159},[150,1054,1055],{"class":155},"not",[150,1057,1058],{"class":155}," in",[150,1060,1061],{"class":159}," centre)\n",[150,1063,1064],{"class":67,"line":300},[150,1065,1066],{"class":155},"            continue\n",[150,1068,1069,1072,1074],{"class":67,"line":306},[150,1070,1071],{"class":159},"        f ",[150,1073,188],{"class":155},[150,1075,1076],{"class":159}," QgsFeature(od.fields())\n",[150,1078,1079,1082,1084,1086,1088,1091,1094,1097,1099],{"class":67,"line":345},[150,1080,1081],{"class":159},"        f.setAttributes([r[",[150,1083,970],{"class":194},[150,1085,1042],{"class":159},[150,1087,976],{"class":194},[150,1089,1090],{"class":159},"], ",[150,1092,1093],{"class":201},"int",[150,1095,1096],{"class":159},"(r[",[150,1098,619],{"class":194},[150,1100,1101],{"class":159},"])])\n",[150,1103,1104],{"class":67,"line":350},[150,1105,1106],{"class":159},"        f.setGeometry(QgsGeometry.fromPolylineXY([QgsPointXY(a), QgsPointXY(b)]))\n",[150,1108,1109],{"class":67,"line":361},[150,1110,1111],{"class":159},"        rows.append(f)\n",[150,1113,1114],{"class":67,"line":372},[150,1115,1116],{"class":159},"od.dataProvider().addFeatures(rows)\n",[150,1118,1119],{"class":67,"line":378},[150,1120,1121],{"class":159},"QgsProject.instance().addMapLayer(od)\n",[150,1123,1125,1127,1129,1132,1135,1138,1140,1142,1145,1148],{"class":67,"line":1124},22,[150,1126,572],{"class":201},[150,1128,575],{"class":159},[150,1130,1131],{"class":201},"len",[150,1133,1134],{"class":159},"(rows), ",[150,1136,1137],{"class":194},"\"flows;\"",[150,1139,321],{"class":159},[150,1141,1131],{"class":201},[150,1143,1144],{"class":159},"(missing), ",[150,1146,1147],{"class":194},"\"unknown place codes\"",[150,1149,232],{"class":159},[14,1151,1152,1154,1155,1159],{},[385,1153,387],{}," Points on surface give each municipality a representative point guaranteed to lie inside it, a better anchor than a centroid for irregular shapes. Each table row becomes a two-point line carrying origin, destination and volume; internal flows (origin equals destination) are skipped because they have no direction to draw. Codes in the table that match no place are collected and reported, which catches code-list mismatches before they silently drop flows from the map. The resulting ",[21,1156,1158],{"href":1157},"\u002Fpyqgis-fundamentals-environment-setup\u002Ffeatures-geometries-and-memory-layers\u002Fcreate-memory-layer-pyqgis\u002F","memory layer"," is ready for the curved, width-scaled arrows above.",[114,1161,1163],{"id":1162},"draw-small-direction-arrows-along-lines","Draw small direction arrows along lines",[14,1165,1166],{},"For networks — one-way streets, river flow, pipe direction — the need is not a big arrow per feature but small chevrons repeated along each line. The arrow layer's repeat mode or a marker line both do it.",[14,1168,1169],{},[29,1170,1172,1175,1178,1180,1183,1187,1190,1193,1196,1199,1204,1207],{"viewBox":424,"role":32,"ariaLabel":1171,"xmlns":34},"Small repeated arrows along one-way street segments showing direction of travel, with two-way streets unmarked",[36,1173,1174],{},"Direction along a network",[40,1176,1177],{},"Small arrows repeated along each street segment show one-way direction without dominating the map. They follow the line's digitizing direction, so lines must be digitized in the direction of travel or reversed where a direction field says so. Two-way streets get no arrows.",[44,1179],{"x":46,"y":46,"width":47,"height":434,"fill":49},[51,1181,1182],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Small, repeated, following the line",[67,1184],{"x1":99,"y1":441,"x2":1185,"y2":441,"stroke":72,"style":1186},"700","stroke-width:6",[60,1188],{"points":1189,"fill":56,"stroke":56,"style":444},"180,112 196,120 180,128",[60,1191],{"points":1192,"fill":56,"stroke":56,"style":444},"340,112 356,120 340,128",[60,1194],{"points":1195,"fill":56,"stroke":56,"style":444},"500,112 516,120 500,128",[60,1197],{"points":1198,"fill":56,"stroke":56,"style":444},"660,112 676,120 660,128",[51,1200,1203],{"x":53,"y":1201,"style":77,"fill":1202,"textAnchor":57},"92","#2f3b35","one-way: arrows every 20 mm",[67,1205],{"x1":99,"y1":1206,"x2":1185,"y2":1206,"stroke":72,"style":1186},"180",[51,1208,1210],{"x":53,"y":1209,"style":77,"fill":72,"textAnchor":57},"210","two-way: no arrows",[141,1212,1214],{"className":143,"code":1213,"language":145,"meta":146,"style":146},"from qgis.core import QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsRuleBasedRenderer\n\nstreets = QgsProject.instance().mapLayersByName(\"streets\")[0]\nbase = QgsLineSymbol.createSimple({\"color\": \"#c9c2ac\", \"width\": \"1.2\"})\nchevrons = QgsMarkerLineSymbolLayer(True, 20)          # rotate with line, every 20 mm\nchevrons.setSubSymbol(QgsMarkerSymbol.createSimple(\n    {\"name\": \"arrowhead\", \"color\": \"#17211d\", \"size\": \"1.6\", \"outline_color\": \"#17211d\"}))\noneway_symbol = base.clone()\noneway_symbol.appendSymbolLayer(chevrons)\n\nroot = QgsRuleBasedRenderer.Rule(None)\nroot.appendChild(QgsRuleBasedRenderer.Rule(oneway_symbol, filterExp='\"oneway\" = \\\\'yes\\\\''))\nroot.appendChild(QgsRuleBasedRenderer.Rule(base.clone(), elseRule=True))\nstreets.setRenderer(QgsRuleBasedRenderer(root))\nstreets.triggerRepaint()\n",[129,1215,1216,1227,1231,1249,1279,1302,1307,1347,1357,1362,1366,1381,1407,1421,1426],{"__ignoreMap":146},[150,1217,1218,1220,1222,1224],{"class":67,"line":152},[150,1219,156],{"class":155},[150,1221,160],{"class":159},[150,1223,163],{"class":155},[150,1225,1226],{"class":159}," QgsMarkerLineSymbolLayer, QgsMarkerSymbol, QgsRuleBasedRenderer\n",[150,1228,1229],{"class":67,"line":169},[150,1230,179],{"emptyLinePlaceholder":178},[150,1232,1233,1236,1238,1240,1243,1245,1247],{"class":67,"line":175},[150,1234,1235],{"class":159},"streets ",[150,1237,188],{"class":155},[150,1239,191],{"class":159},[150,1241,1242],{"class":194},"\"streets\"",[150,1244,198],{"class":159},[150,1246,46],{"class":201},[150,1248,204],{"class":159},[150,1250,1251,1254,1256,1259,1261,1263,1266,1268,1271,1273,1276],{"class":67,"line":182},[150,1252,1253],{"class":159},"base ",[150,1255,188],{"class":155},[150,1257,1258],{"class":159}," QgsLineSymbol.createSimple({",[150,1260,312],{"class":194},[150,1262,315],{"class":159},[150,1264,1265],{"class":194},"\"#c9c2ac\"",[150,1267,321],{"class":159},[150,1269,1270],{"class":194},"\"width\"",[150,1272,315],{"class":159},[150,1274,1275],{"class":194},"\"1.2\"",[150,1277,1278],{"class":159},"})\n",[150,1280,1281,1284,1286,1289,1291,1293,1296,1299],{"class":67,"line":207},[150,1282,1283],{"class":159},"chevrons ",[150,1285,188],{"class":155},[150,1287,1288],{"class":159}," QgsMarkerLineSymbolLayer(",[150,1290,494],{"class":201},[150,1292,321],{"class":159},[150,1294,1295],{"class":201},"20",[150,1297,1298],{"class":159},")          ",[150,1300,1301],{"class":525},"# rotate with line, every 20 mm\n",[150,1303,1304],{"class":67,"line":212},[150,1305,1306],{"class":159},"chevrons.setSubSymbol(QgsMarkerSymbol.createSimple(\n",[150,1308,1309,1311,1314,1316,1319,1321,1323,1325,1327,1329,1332,1334,1337,1339,1341,1343,1345],{"class":67,"line":223},[150,1310,309],{"class":159},[150,1312,1313],{"class":194},"\"name\"",[150,1315,315],{"class":159},[150,1317,1318],{"class":194},"\"arrowhead\"",[150,1320,321],{"class":159},[150,1322,312],{"class":194},[150,1324,315],{"class":159},[150,1326,329],{"class":194},[150,1328,321],{"class":159},[150,1330,1331],{"class":194},"\"size\"",[150,1333,315],{"class":159},[150,1335,1336],{"class":194},"\"1.6\"",[150,1338,321],{"class":159},[150,1340,324],{"class":194},[150,1342,315],{"class":159},[150,1344,329],{"class":194},[150,1346,342],{"class":159},[150,1348,1349,1352,1354],{"class":67,"line":235},[150,1350,1351],{"class":159},"oneway_symbol ",[150,1353,188],{"class":155},[150,1355,1356],{"class":159}," base.clone()\n",[150,1358,1359],{"class":67,"line":245},[150,1360,1361],{"class":159},"oneway_symbol.appendSymbolLayer(chevrons)\n",[150,1363,1364],{"class":67,"line":256},[150,1365,179],{"emptyLinePlaceholder":178},[150,1367,1368,1371,1373,1376,1379],{"class":67,"line":267},[150,1369,1370],{"class":159},"root ",[150,1372,188],{"class":155},[150,1374,1375],{"class":159}," QgsRuleBasedRenderer.Rule(",[150,1377,1378],{"class":201},"None",[150,1380,232],{"class":159},[150,1382,1383,1386,1389,1391,1394,1397,1400,1403],{"class":67,"line":273},[150,1384,1385],{"class":159},"root.appendChild(QgsRuleBasedRenderer.Rule(oneway_symbol, ",[150,1387,1388],{"class":919},"filterExp",[150,1390,188],{"class":155},[150,1392,1393],{"class":194},"'\"oneway\" = ",[150,1395,1396],{"class":201},"\\\\",[150,1398,1399],{"class":194},"'",[150,1401,1402],{"class":159},"yes\\",[150,1404,1406],{"class":1405},"s6RL2","\\''))\n",[150,1408,1409,1412,1415,1417,1419],{"class":67,"line":279},[150,1410,1411],{"class":159},"root.appendChild(QgsRuleBasedRenderer.Rule(base.clone(), ",[150,1413,1414],{"class":919},"elseRule",[150,1416,188],{"class":155},[150,1418,494],{"class":201},[150,1420,708],{"class":159},[150,1422,1423],{"class":67,"line":290},[150,1424,1425],{"class":159},"streets.setRenderer(QgsRuleBasedRenderer(root))\n",[150,1427,1428],{"class":67,"line":300},[150,1429,1430],{"class":159},"streets.triggerRepaint()\n",[14,1432,1433,1435,1436,1439,1440,1444],{},[385,1434,387],{}," A marker line places an arrowhead marker every 20 mm, rotated to follow the line, on top of the street's normal line. A rule applies the chevrons only to one-way streets. Arrows point in the digitizing direction, so the data must be digitized in the direction of travel; for networks with a ",[129,1437,1438],{},"oneway = -1"," convention, add a rule that flips the marker rotation by 180 degrees. Marker lines and their placement options are covered in ",[21,1441,1443],{"href":1442},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fmarker-line-and-interval-symbols-pyqgis\u002F","marker line and interval symbols",".",[114,1446,1448],{"id":1447},"make-flow-maps-readable","Make flow maps readable",[14,1450,1451],{},"Flow maps fail when too many arrows cross. A few rules keep them legible: draw the largest flows on top by ordering features by volume ascending, so small arrows go underneath; drop flows below a threshold or show them as a separate, faint layer; use a semi-transparent fill so overlapping arrows show through; and keep internal flows (a place to itself) off the map. Feature ordering is set on the renderer:",[141,1453,1455],{"className":143,"code":1454,"language":145,"meta":146,"style":146},"renderer = flows.renderer()\nrenderer.setOrderByEnabled(True)\nfrom qgis.core import QgsFeatureRequest\nrenderer.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause('\"commuters\"', True)]))\nflows.triggerRepaint()\n",[129,1456,1457,1467,1476,1487,1502],{"__ignoreMap":146},[150,1458,1459,1462,1464],{"class":67,"line":152},[150,1460,1461],{"class":159},"renderer ",[150,1463,188],{"class":155},[150,1465,1466],{"class":159}," flows.renderer()\n",[150,1468,1469,1472,1474],{"class":67,"line":169},[150,1470,1471],{"class":159},"renderer.setOrderByEnabled(",[150,1473,494],{"class":201},[150,1475,232],{"class":159},[150,1477,1478,1480,1482,1484],{"class":67,"line":175},[150,1479,156],{"class":155},[150,1481,160],{"class":159},[150,1483,163],{"class":155},[150,1485,1486],{"class":159}," QgsFeatureRequest\n",[150,1488,1489,1492,1495,1497,1499],{"class":67,"line":182},[150,1490,1491],{"class":159},"renderer.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause(",[150,1493,1494],{"class":194},"'\"commuters\"'",[150,1496,321],{"class":159},[150,1498,494],{"class":201},[150,1500,1501],{"class":159},")]))\n",[150,1503,1504],{"class":67,"line":207},[150,1505,381],{"class":159},[14,1507,1508,1510],{},[385,1509,387],{}," Ordering by volume ascending draws the smallest flows first and the largest last, so major flows are never hidden under minor ones. Combined with square-root widths, a minimum-flow filter and translucent fills, this is usually enough to make a regional commuter map readable without manual editing.",[114,1512,1514],{"id":1513},"qgis-version-compatibility","QGIS version compatibility",[14,1516,1517,1520,1521,321,1524,1527,1528,1444],{},[129,1518,1519],{},"QgsArrowSymbolLayer"," and its data-defined properties are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. On QGIS 4, enums are scoped: ",[129,1522,1523],{},"QgsArrowSymbolLayer.HeadType.HeadSingle",[129,1525,1526],{},"QgsArrowSymbolLayer.ArrowType.ArrowPlain"," and ",[129,1529,1530],{},"QgsSymbolLayer.Property.ArrowWidth",[114,1532,1534],{"id":1533},"troubleshooting","Troubleshooting",[119,1536,1537,1543,1549,1555],{},[122,1538,1539,1542],{},[385,1540,1541],{},"Arrows point the wrong way."," Lines are digitized backwards; reverse them or use a reversed head type.",[122,1544,1545,1548],{},[385,1546,1547],{},"Curved arrows are straight."," The lines have only two vertices; add a midpoint.",[122,1550,1551,1554],{},[385,1552,1553],{},"Huge arrows swamp the map."," Width scales linearly with large values; use a square root and normalise.",[122,1556,1557,1560,1561,1563],{},[385,1558,1559],{},"Arrows are drawn per segment."," Repetition is on; set ",[129,1562,411],{}," for one arrow per feature.",[114,1565,1567],{"id":1566},"conclusion","Conclusion",[14,1569,1570,1571,1573],{},"Use ",[129,1572,1519],{}," for arrows along lines, set shaft and head sizes deliberately, curve flows with an offset midpoint so opposite directions separate, scale widths by the square root of volume with heads following, use repeated markers for direction on networks, and order, filter and lighten flows so the map stays legible.",[114,1575,1577],{"id":1576},"frequently-asked-questions","Frequently Asked Questions",[14,1579,1580,1583],{},[385,1581,1582],{},"Can arrows have gradient fills?","\nYes — use a gradient fill as the sub-symbol.",[14,1585,1586,1589,1590,1593],{},[385,1587,1588],{},"Can I draw arrows between points without making lines?","\nUse a geometry generator with ",[129,1591,1592],{},"make_line"," between two point geometries, for example via a relation.",[14,1595,1596,1599],{},[385,1597,1598],{},"Do arrows export to SVG?","\nYes, as vector shapes.",[14,1601,1602,1605],{},[385,1603,1604],{},"How do I add a legend for flow widths?","\nUse a data-defined size legend on the renderer, or draw three reference arrows for round volumes in the layout so readers can compare.",[14,1607,1608,1611],{},[385,1609,1610],{},"Can arrows be animated?","\nWith the temporal controller, flows can appear over time; the arrows themselves are static.",[114,1613,1615],{"id":1614},"related","Related",[119,1617,1618,1623,1628,1633,1639],{},[122,1619,1620,1622],{},[21,1621,24],{"href":23}," — the guide this recipe belongs to",[122,1624,1625],{},[21,1626,1627],{"href":1442},"Marker Line and Interval Symbols in PyQGIS",[122,1629,1630],{},[21,1631,1632],{"href":589},"Geometry Generator Symbol Layer in PyQGIS",[122,1634,1635],{},[21,1636,1638],{"href":1637},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fstack-symbol-layers-pyqgis\u002F","Stack Symbol Layers in PyQGIS",[122,1640,1641],{},[21,1642,1644],{"href":1643},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fdata-defined-symbol-size-expression-pyqgis\u002F","Data-Defined Symbol Size with Expressions",[1646,1647,1648],"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}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .s6RL2, html code.shiki .s6RL2{--shiki-default:#FDAEB7;--shiki-default-font-style:italic}",{"title":146,"searchDepth":169,"depth":169,"links":1650},[1651,1652,1653,1654,1655,1656,1657,1658,1659,1660,1661,1662],{"id":116,"depth":169,"text":117},{"id":135,"depth":169,"text":136},{"id":415,"depth":169,"text":416},{"id":594,"depth":169,"text":595},{"id":764,"depth":169,"text":765},{"id":1162,"depth":169,"text":1163},{"id":1447,"depth":169,"text":1448},{"id":1513,"depth":169,"text":1514},{"id":1533,"depth":169,"text":1534},{"id":1566,"depth":169,"text":1567},{"id":1576,"depth":169,"text":1577},{"id":1614,"depth":169,"text":1615},"Draw flows, directions and movements with QgsArrowSymbolLayer — single and double-headed arrows, curved arrows, head and shaft sizes, widths driven by attributes for flow maps, fill styling, and arrows that follow line direction for one-way streets and river flow.","md",{"slug":1666,"type":1667,"breadcrumb":1668,"datePublished":1669,"dateModified":1669},"arrow-symbol-layer-pyqgis","article","Use the Arrow Symbol Layer","2026-10-02","\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Farrow-symbol-layer-pyqgis",{"title":5,"description":1663},"pyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Farrow-symbol-layer-pyqgis\u002Findex","CCgCqzT5sHJlpjD3QeBSXAiGg4IHBPzBu3FMZdb4OFw",1790966252917]