[{"data":1,"prerenderedAt":1687},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology":3},{"id":4,"title":5,"body":6,"description":1676,"extension":1677,"meta":1678,"navigation":286,"path":1683,"seo":1684,"stem":1685,"__hash__":1686},"docs\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Findex.md","Symbol Layers & Advanced Symbology in PyQGIS",{"type":7,"value":8,"toc":1662},"minimark",[9,13,26,45,209,214,217,254,377,414,425,429,432,615,627,634,692,711,715,720,737,833,855,863,867,870,978,999,1006,1113,1117,1124,1132,1136,1151,1171,1175,1178,1192,1337,1357,1384,1388,1391,1403,1458,1466,1470,1473,1476,1484,1488,1527,1531,1547,1563,1573,1579,1594,1610,1614,1658],[10,11,5],"h1",{"id":12},"symbol-layers-advanced-symbology-in-pyqgis",[14,15,16,17,21,22,25],"p",{},"A renderer decides ",[18,19,20],"em",{},"which"," symbol each feature gets. A symbol decides ",[18,23,24],{},"how"," that feature is drawn — and in QGIS a symbol is never a single instruction. It is a stack of symbol layers, drawn bottom to top, each with its own colour, width, offset and its own set of properties that can be driven by an expression. Everything that looks sophisticated in QGIS cartography — a road casing, a dashed border inside a solid one, a hatched fill under a crisp outline, a marker that grows with a value — is that stack being used properly.",[14,27,28,29,34,35,39,40,44],{},"This guide sits inside ",[30,31,33],"a",{"href":32},"\u002Fpyqgis-cartography-visualization\u002F","PyQGIS Cartography & Data Visualization"," and picks up where ",[30,36,38],{"href":37},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002F","programmatic layer styling"," stops. Styling gets you a symbol with a colour; symbology gets you a symbol that carries information. If you are choosing symbols per feature class rather than composing one, ",[30,41,43],{"href":42},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002F","graduated and categorized renderers"," is the page you want first, then come back here to make each of those classes look like something.",[14,46,47],{},[48,49,54,58,62,69,86,95,104,110,115,121,125,128,133,137,140,146,150,153,160,164,168,176,180,188,192,197,201,205],"svg",{"viewBox":50,"role":51,"ariaLabel":52,"xmlns":53},"0 0 760 340","img","The QGIS symbology object model: a layer holds a renderer, the renderer holds symbols, and each symbol holds an ordered stack of symbol layers drawn from bottom to top","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[55,56,57],"title",{},"Layer, renderer, symbol, symbol layer",[59,60,61],"desc",{},"A vector layer owns one renderer. The renderer owns one or more symbols, chosen per feature. Each symbol owns an ordered list of symbol layers. The symbol layers are painted in index order, so index zero is painted first and appears underneath everything above it.",[63,64],"rect",{"x":65,"y":65,"width":66,"height":67,"fill":68},"0","760","340","#f6f3ea",[70,71,72],"defs",{},[73,74,81],"marker",{"id":75,"viewBox":76,"refX":77,"refY":78,"markerWidth":79,"markerHeight":79,"orient":80},"slaArrow","0 0 10 10","8","5","7","auto-start-reverse",[82,83],"path",{"d":84,"fill":85},"M0 0 L10 5 L0 10 z","#2f3b35",[87,88,94],"text",{"x":89,"y":90,"style":91,"fill":92,"textAnchor":93},"380","30","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","One layer, one renderer, many symbols, each a stack",[63,96],{"x":97,"y":98,"width":99,"height":100,"rx":77,"fill":101,"stroke":102,"style":103},"24","60","150","70","#eef7f4","#0f766e","stroke-width:2.5",[87,105,109],{"x":106,"y":107,"style":108,"fill":102,"textAnchor":93},"99","88","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","QgsVectorLayer",[87,111,114],{"x":106,"y":112,"style":113,"fill":85,"textAnchor":93},"110","text-anchor:middle;font-size:11px;font-family:sans-serif","the data + its style",[63,116],{"x":117,"y":98,"width":118,"height":100,"rx":77,"fill":119,"stroke":120,"style":103},"214","160","#eff3ff","#2563eb",[87,122,124],{"x":123,"y":107,"style":108,"fill":120,"textAnchor":93},"294","renderer",[87,126,127],{"x":123,"y":112,"style":113,"fill":85,"textAnchor":93},"picks a symbol per feature",[63,129],{"x":130,"y":98,"width":99,"height":100,"rx":77,"fill":131,"stroke":132,"style":103},"414","#fdf2e2","#b45309",[87,134,136],{"x":135,"y":107,"style":108,"fill":132,"textAnchor":93},"489","QgsSymbol",[87,138,139],{"x":135,"y":112,"style":113,"fill":85,"textAnchor":93},"a stack, not a colour",[63,141],{"x":142,"y":98,"width":143,"height":100,"rx":77,"fill":144,"stroke":145,"style":103},"604","136","#edf8e9","#15803d",[87,147,149],{"x":148,"y":107,"style":108,"fill":145,"textAnchor":93},"672","symbol layers",[87,151,152],{"x":148,"y":112,"style":113,"fill":85,"textAnchor":93},"ordered list",[154,155],"line",{"x1":156,"y1":157,"x2":158,"y2":157,"stroke":85,"style":159},"174","95","208","stroke-width:2;marker-end:url(#slaArrow)",[154,161],{"x1":162,"y1":157,"x2":163,"y2":157,"stroke":85,"style":159},"374","408",[154,165],{"x1":166,"y1":157,"x2":167,"y2":157,"stroke":85,"style":159},"564","598",[63,169],{"x":99,"y":170,"width":171,"height":172,"rx":173,"fill":174,"stroke":102,"style":175},"170","460","130","10","#fffdf7","stroke-width:2",[87,177,179],{"x":89,"y":178,"style":108,"fill":92,"textAnchor":93},"196","symbol.symbolLayers() — painted in index order",[63,181],{"x":182,"y":183,"width":163,"height":97,"rx":184,"fill":185,"stroke":186,"style":187},"176","212","4","#e7e2d4","#59645f","stroke-width:1.5",[87,189,191],{"x":89,"y":190,"style":113,"fill":85,"textAnchor":93},"229","index 0 — wide dark line (the casing), painted first",[63,193],{"x":178,"y":194,"width":195,"height":97,"rx":184,"fill":196,"stroke":120,"style":187},"244","368","#dbeafe",[87,198,200],{"x":89,"y":199,"style":113,"fill":85,"textAnchor":93},"261","index 1 — narrow bright line, painted on top",[87,202,204],{"x":89,"y":203,"style":113,"fill":186,"textAnchor":93},"290","reverse the order and the casing hides the road",[87,206,208],{"x":89,"y":207,"style":113,"fill":186,"textAnchor":93},"326","every property on every layer can be replaced by an expression",[210,211,213],"h2",{"id":212},"the-symbol-model-in-the-order-you-will-use-it","The symbol model, in the order you will use it",[14,215,216],{},"Three classes carry almost all of the work, and knowing which one you are holding removes most of the confusion around the API.",[14,218,219,222,223,226,227,230,231,234,235,238,239,241,242,245,246,249,250,253],{},[220,221,136],"code",{}," is abstract; you always hold one of ",[220,224,225],{},"QgsMarkerSymbol",", ",[220,228,229],{},"QgsLineSymbol"," or ",[220,232,233],{},"QgsFillSymbol",", matched to the layer's geometry type. Each owns an ordered list of ",[220,236,237],{},"QgsSymbolLayer"," objects. A ",[220,240,237],{}," is where the drawing instructions actually live: ",[220,243,244],{},"QgsSimpleLineSymbolLayer"," knows about width and dash pattern, ",[220,247,248],{},"QgsSvgMarkerSymbolLayer"," knows about a path to an SVG file, ",[220,251,252],{},"QgsSimpleFillSymbolLayer"," knows about a brush style and a stroke.",[255,256,261],"pre",{"className":257,"code":258,"language":259,"meta":260,"style":260},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsLineSymbol\n\nlayer = QgsProject.instance().mapLayersByName(\"roads\")[0]\nsymbol = layer.renderer().symbol()\n\nprint(type(symbol).__name__, symbol.symbolLayerCount())\nfor index, symbol_layer in enumerate(symbol.symbolLayers()):\n    print(index, symbol_layer.layerType(), symbol_layer.color().name())\n","python","",[220,262,263,281,288,313,324,329,350,368],{"__ignoreMap":260},[264,265,267,271,275,278],"span",{"class":154,"line":266},1,[264,268,270],{"class":269},"snl16","from",[264,272,274],{"class":273},"s95oV"," qgis.core ",[264,276,277],{"class":269},"import",[264,279,280],{"class":273}," QgsProject, QgsLineSymbol\n",[264,282,284],{"class":154,"line":283},2,[264,285,287],{"emptyLinePlaceholder":286},true,"\n",[264,289,291,294,297,300,304,307,310],{"class":154,"line":290},3,[264,292,293],{"class":273},"layer ",[264,295,296],{"class":269},"=",[264,298,299],{"class":273}," QgsProject.instance().mapLayersByName(",[264,301,303],{"class":302},"sU2Wk","\"roads\"",[264,305,306],{"class":273},")[",[264,308,65],{"class":309},"sDLfK",[264,311,312],{"class":273},"]\n",[264,314,316,319,321],{"class":154,"line":315},4,[264,317,318],{"class":273},"symbol ",[264,320,296],{"class":269},[264,322,323],{"class":273}," layer.renderer().symbol()\n",[264,325,327],{"class":154,"line":326},5,[264,328,287],{"emptyLinePlaceholder":286},[264,330,332,335,338,341,344,347],{"class":154,"line":331},6,[264,333,334],{"class":309},"print",[264,336,337],{"class":273},"(",[264,339,340],{"class":309},"type",[264,342,343],{"class":273},"(symbol).",[264,345,346],{"class":309},"__name__",[264,348,349],{"class":273},", symbol.symbolLayerCount())\n",[264,351,353,356,359,362,365],{"class":154,"line":352},7,[264,354,355],{"class":269},"for",[264,357,358],{"class":273}," index, symbol_layer ",[264,360,361],{"class":269},"in",[264,363,364],{"class":309}," enumerate",[264,366,367],{"class":273},"(symbol.symbolLayers()):\n",[264,369,371,374],{"class":154,"line":370},8,[264,372,373],{"class":309},"    print",[264,375,376],{"class":273},"(index, symbol_layer.layerType(), symbol_layer.color().name())\n",[14,378,379,383,384,387,388,391,392,395,396,399,400,403,404,226,407,226,410,413],{},[380,381,382],"strong",{},"Breakdown:"," ",[220,385,386],{},"renderer().symbol()"," only exists on a single-symbol renderer; a categorized or graduated renderer exposes ",[220,389,390],{},"symbols(QgsRenderContext())"," or per-category ",[220,393,394],{},"symbol()"," instead, which is the first thing to check when this line raises ",[220,397,398],{},"AttributeError",". ",[220,401,402],{},"layerType()"," returns the registry string — ",[220,405,406],{},"SimpleLine",[220,408,409],{},"SvgMarker",[220,411,412],{},"GeometryGenerator"," — and that string is the same one you pass when creating a layer from a properties dictionary, so printing it on a symbol you built in the GUI is the quickest way to learn the name of something you liked.",[14,415,416,417,420,421,424],{},"Because the symbol is retrieved by reference from the renderer, changes made to it are live, but the canvas does not know. Any script that edits symbology finishes with ",[220,418,419],{},"layer.triggerRepaint()",", and any script that also wants the legend to catch up calls ",[220,422,423],{},"iface.layerTreeView().refreshLayerSymbology(layer.id())",".",[210,426,428],{"id":427},"composing-a-stack-casings-inner-lines-and-hatches","Composing a stack: casings, inner lines and hatches",[14,430,431],{},"The road-casing pattern is the canonical example, and it generalises to every \"outline plus something\" effect.",[255,433,435],{"className":257,"code":434,"language":259,"meta":260,"style":260},"from qgis.core import QgsLineSymbol, QgsSimpleLineSymbolLayer\nfrom qgis.PyQt.QtGui import QColor\nfrom qgis.PyQt.QtCore import Qt\n\nsymbol = QgsLineSymbol()\nsymbol.deleteSymbolLayer(0)                       # drop the default layer\n\ncasing = QgsSimpleLineSymbolLayer(QColor(\"#3f3f46\"))\ncasing.setWidth(1.6)\ncasing.setPenCapStyle(Qt.RoundCap)\ncasing.setPenJoinStyle(Qt.RoundJoin)\n\nfill = QgsSimpleLineSymbolLayer(QColor(\"#fbbf24\"))\nfill.setWidth(1.0)\nfill.setPenCapStyle(Qt.RoundCap)\nfill.setPenJoinStyle(Qt.RoundJoin)\n\nsymbol.appendSymbolLayer(casing)\nsymbol.appendSymbolLayer(fill)\nlayer.renderer().setSymbol(symbol)\nlayer.triggerRepaint()\n",[220,436,437,448,460,472,476,485,499,503,519,531,537,543,548,563,574,580,586,591,597,603,609],{"__ignoreMap":260},[264,438,439,441,443,445],{"class":154,"line":266},[264,440,270],{"class":269},[264,442,274],{"class":273},[264,444,277],{"class":269},[264,446,447],{"class":273}," QgsLineSymbol, QgsSimpleLineSymbolLayer\n",[264,449,450,452,455,457],{"class":154,"line":283},[264,451,270],{"class":269},[264,453,454],{"class":273}," qgis.PyQt.QtGui ",[264,456,277],{"class":269},[264,458,459],{"class":273}," QColor\n",[264,461,462,464,467,469],{"class":154,"line":290},[264,463,270],{"class":269},[264,465,466],{"class":273}," qgis.PyQt.QtCore ",[264,468,277],{"class":269},[264,470,471],{"class":273}," Qt\n",[264,473,474],{"class":154,"line":315},[264,475,287],{"emptyLinePlaceholder":286},[264,477,478,480,482],{"class":154,"line":326},[264,479,318],{"class":273},[264,481,296],{"class":269},[264,483,484],{"class":273}," QgsLineSymbol()\n",[264,486,487,490,492,495],{"class":154,"line":331},[264,488,489],{"class":273},"symbol.deleteSymbolLayer(",[264,491,65],{"class":309},[264,493,494],{"class":273},")                       ",[264,496,498],{"class":497},"sjoCn","# drop the default layer\n",[264,500,501],{"class":154,"line":352},[264,502,287],{"emptyLinePlaceholder":286},[264,504,505,508,510,513,516],{"class":154,"line":370},[264,506,507],{"class":273},"casing ",[264,509,296],{"class":269},[264,511,512],{"class":273}," QgsSimpleLineSymbolLayer(QColor(",[264,514,515],{"class":302},"\"#3f3f46\"",[264,517,518],{"class":273},"))\n",[264,520,522,525,528],{"class":154,"line":521},9,[264,523,524],{"class":273},"casing.setWidth(",[264,526,527],{"class":309},"1.6",[264,529,530],{"class":273},")\n",[264,532,534],{"class":154,"line":533},10,[264,535,536],{"class":273},"casing.setPenCapStyle(Qt.RoundCap)\n",[264,538,540],{"class":154,"line":539},11,[264,541,542],{"class":273},"casing.setPenJoinStyle(Qt.RoundJoin)\n",[264,544,546],{"class":154,"line":545},12,[264,547,287],{"emptyLinePlaceholder":286},[264,549,551,554,556,558,561],{"class":154,"line":550},13,[264,552,553],{"class":273},"fill ",[264,555,296],{"class":269},[264,557,512],{"class":273},[264,559,560],{"class":302},"\"#fbbf24\"",[264,562,518],{"class":273},[264,564,566,569,572],{"class":154,"line":565},14,[264,567,568],{"class":273},"fill.setWidth(",[264,570,571],{"class":309},"1.0",[264,573,530],{"class":273},[264,575,577],{"class":154,"line":576},15,[264,578,579],{"class":273},"fill.setPenCapStyle(Qt.RoundCap)\n",[264,581,583],{"class":154,"line":582},16,[264,584,585],{"class":273},"fill.setPenJoinStyle(Qt.RoundJoin)\n",[264,587,589],{"class":154,"line":588},17,[264,590,287],{"emptyLinePlaceholder":286},[264,592,594],{"class":154,"line":593},18,[264,595,596],{"class":273},"symbol.appendSymbolLayer(casing)\n",[264,598,600],{"class":154,"line":599},19,[264,601,602],{"class":273},"symbol.appendSymbolLayer(fill)\n",[264,604,606],{"class":154,"line":605},20,[264,607,608],{"class":273},"layer.renderer().setSymbol(symbol)\n",[264,610,612],{"class":154,"line":611},21,[264,613,614],{"class":273},"layer.triggerRepaint()\n",[14,616,617,619,620,622,623,626],{},[380,618,382],{}," A freshly constructed ",[220,621,229],{}," already contains one simple line layer, so the first move is almost always ",[220,624,625],{},"deleteSymbolLayer(0)"," — otherwise the default blue line sits underneath everything you add and shows at the joins. Round caps and joins on both layers matter more than they look: with the default flat caps, the casing stops exactly where the geometry stops and the bright fill pokes out past it at every segment end.",[14,628,629,630,633],{},"Widths are in millimetres by default. ",[220,631,632],{},"setOutputUnit(QgsUnitTypes.RenderMetersInMapUnits)"," switches a symbol layer to ground units, which is what you want when a road's width should shrink as you zoom out and stay true to its real width — and what you do not want for a hairline that must stay legible at every scale.",[14,635,636],{},[48,637,640,643,646,649,652,657,662,668,671,674,678,682,685,688],{"viewBox":638,"role":51,"ariaLabel":639,"xmlns":53},"0 0 760 300","Two line symbols compared: one with a single line layer and flat caps, and one with a wide casing under a narrow fill using round caps, showing the difference at the segment join",[55,641,642],{},"Why a casing needs round joins",[59,644,645],{},"The upper example draws a single yellow line. The lower example draws a dark casing first and a narrower yellow line on top. At the corner the flat-capped version leaves the bright line protruding past the casing, while the round-joined version keeps the casing enclosing the fill all the way round the bend.",[63,647],{"x":65,"y":65,"width":66,"height":648,"fill":68},"300",[87,650,651],{"x":89,"y":90,"style":91,"fill":92,"textAnchor":93},"The stack is only convincing if the caps and joins agree",[63,653],{"x":90,"y":654,"width":655,"height":656,"rx":173,"fill":131,"stroke":132,"style":175},"52","330","210",[87,658,661],{"x":659,"y":660,"style":108,"fill":132,"textAnchor":93},"195","78","one layer, flat caps",[82,663],{"d":664,"fill":665,"stroke":666,"style":667},"M70 210 L170 120 L320 150","none","#f59e0b","stroke-width:12;stroke-linecap:butt;stroke-linejoin:miter",[87,669,670],{"x":659,"y":194,"style":113,"fill":85,"textAnchor":93},"no casing — the line has no edge to read against",[63,672],{"x":673,"y":654,"width":655,"height":656,"rx":173,"fill":144,"stroke":145,"style":175},"400",[87,675,677],{"x":676,"y":660,"style":108,"fill":145,"textAnchor":93},"565","casing + fill, round joins",[82,679],{"d":680,"fill":665,"stroke":92,"style":681},"M440 210 L540 120 L690 150","stroke-width:18;stroke-linecap:round;stroke-linejoin:round",[82,683],{"d":680,"fill":665,"stroke":666,"style":684},"stroke-width:10;stroke-linecap:round;stroke-linejoin:round",[87,686,687],{"x":676,"y":194,"style":113,"fill":85,"textAnchor":93},"the dark layer encloses the bright one at the bend",[87,689,691],{"x":89,"y":690,"style":113,"fill":186,"textAnchor":93},"288","index 0 is painted first, so the casing must be added before the fill",[14,693,694,695,698,699,701,702,705,706,710],{},"Fills work the same way. A hatched fill under a solid outline is a ",[220,696,697],{},"QgsLinePatternFillSymbolLayer"," at index 0 and a ",[220,700,252],{}," with ",[220,703,704],{},"Qt.NoBrush"," on top, so the outline is crisp and the hatch never overruns it. The ",[30,707,709],{"href":708},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fstack-symbol-layers-pyqgis\u002F","full recipe for stacking symbol layers"," walks through both cases with the property dictionaries.",[210,712,714],{"id":713},"markers-beyond-the-circle","Markers beyond the circle",[14,716,717,719],{},[220,718,225],{}," accepts several kinds of marker layer, and the choice is mostly about where the artwork comes from.",[14,721,722,725,726,728,729,732,733,736],{},[220,723,724],{},"QgsSimpleMarkerSymbolLayer"," draws from a fixed set of shapes and is the fastest by a wide margin. ",[220,727,248],{}," reads an SVG from disk or from the QGIS SVG search paths; if the SVG uses the QGIS parameter convention it can be recoloured and re-stroked per feature. ",[220,730,731],{},"QgsFontMarkerSymbolLayer"," renders one character from an installed font, which is how icon fonts get onto a map without shipping a directory of files. ",[220,734,735],{},"QgsRasterMarkerSymbolLayer"," places a PNG, and is the right answer only when the artwork genuinely is a photograph or a logo that must not be re-drawn.",[255,738,740],{"className":257,"code":739,"language":259,"meta":260,"style":260},"from qgis.core import QgsMarkerSymbol\n\nsymbol = QgsMarkerSymbol.createSimple({\n    \"name\": \"circle\",\n    \"color\": \"#2563eb\",\n    \"outline_color\": \"#1e40af\",\n    \"outline_width\": \"0.4\",\n    \"size\": \"3\",\n})\n",[220,741,742,753,757,766,780,792,804,816,828],{"__ignoreMap":260},[264,743,744,746,748,750],{"class":154,"line":266},[264,745,270],{"class":269},[264,747,274],{"class":273},[264,749,277],{"class":269},[264,751,752],{"class":273}," QgsMarkerSymbol\n",[264,754,755],{"class":154,"line":283},[264,756,287],{"emptyLinePlaceholder":286},[264,758,759,761,763],{"class":154,"line":290},[264,760,318],{"class":273},[264,762,296],{"class":269},[264,764,765],{"class":273}," QgsMarkerSymbol.createSimple({\n",[264,767,768,771,774,777],{"class":154,"line":315},[264,769,770],{"class":302},"    \"name\"",[264,772,773],{"class":273},": ",[264,775,776],{"class":302},"\"circle\"",[264,778,779],{"class":273},",\n",[264,781,782,785,787,790],{"class":154,"line":326},[264,783,784],{"class":302},"    \"color\"",[264,786,773],{"class":273},[264,788,789],{"class":302},"\"#2563eb\"",[264,791,779],{"class":273},[264,793,794,797,799,802],{"class":154,"line":331},[264,795,796],{"class":302},"    \"outline_color\"",[264,798,773],{"class":273},[264,800,801],{"class":302},"\"#1e40af\"",[264,803,779],{"class":273},[264,805,806,809,811,814],{"class":154,"line":352},[264,807,808],{"class":302},"    \"outline_width\"",[264,810,773],{"class":273},[264,812,813],{"class":302},"\"0.4\"",[264,815,779],{"class":273},[264,817,818,821,823,826],{"class":154,"line":370},[264,819,820],{"class":302},"    \"size\"",[264,822,773],{"class":273},[264,824,825],{"class":302},"\"3\"",[264,827,779],{"class":273},[264,829,830],{"class":154,"line":521},[264,831,832],{"class":273},"})\n",[14,834,835,383,837,840,841,844,845,848,849,851,852,424],{},[380,836,382],{},[220,838,839],{},"createSimple()"," takes the same property dictionary the symbol layer's ",[220,842,843],{},"properties()"," method returns, so the fastest way to build one of these is to style a layer in the GUI, read the dictionary back in the console, and paste it into the script. The keys are documented per symbol-layer class and are not consistent across them — a marker uses ",[220,846,847],{},"outline_color",", a fill uses ",[220,850,847],{}," too, but a simple line calls the same idea ",[220,853,854],{},"line_color",[14,856,857,858,862],{},"Sizing and rotation are where markers earn their keep. Both accept a data-defined override, so ",[30,859,861],{"href":860},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-proportional-symbol-map-pyqgis\u002F","a marker whose size encodes a value"," is a two-line change rather than a new renderer, and a marker whose rotation follows a bearing field turns a point layer into a flow map.",[210,864,866],{"id":865},"data-defined-overrides-the-property-that-is-really-an-expression","Data-defined overrides: the property that is really an expression",[14,868,869],{},"Every symbol-layer property is addressable by a property key, and any of them can be replaced with an expression evaluated per feature. This is the mechanism behind data-defined size, colour, offset, rotation and dash pattern.",[255,871,873],{"className":257,"code":872,"language":259,"meta":260,"style":260},"from qgis.core import QgsProperty, QgsSymbolLayer\n\nmarker = symbol.symbolLayer(0)\nmarker.setDataDefinedProperty(\n    QgsSymbolLayer.PropertySize,\n    QgsProperty.fromExpression(\"coalesce(sqrt(\\\"population\\\") \u002F 40, 1)\"),\n)\nmarker.setDataDefinedProperty(\n    QgsSymbolLayer.PropertyFillColor,\n    QgsProperty.fromExpression(\n        \"case when \\\"status\\\" = 'closed' then '#b91c1c' else '#15803d' end\"\n    ),\n)\n",[220,874,875,886,890,904,909,914,936,940,944,949,954,969,974],{"__ignoreMap":260},[264,876,877,879,881,883],{"class":154,"line":266},[264,878,270],{"class":269},[264,880,274],{"class":273},[264,882,277],{"class":269},[264,884,885],{"class":273}," QgsProperty, QgsSymbolLayer\n",[264,887,888],{"class":154,"line":283},[264,889,287],{"emptyLinePlaceholder":286},[264,891,892,895,897,900,902],{"class":154,"line":290},[264,893,894],{"class":273},"marker ",[264,896,296],{"class":269},[264,898,899],{"class":273}," symbol.symbolLayer(",[264,901,65],{"class":309},[264,903,530],{"class":273},[264,905,906],{"class":154,"line":315},[264,907,908],{"class":273},"marker.setDataDefinedProperty(\n",[264,910,911],{"class":154,"line":326},[264,912,913],{"class":273},"    QgsSymbolLayer.PropertySize,\n",[264,915,916,919,922,925,928,930,933],{"class":154,"line":331},[264,917,918],{"class":273},"    QgsProperty.fromExpression(",[264,920,921],{"class":302},"\"coalesce(sqrt(",[264,923,924],{"class":309},"\\\"",[264,926,927],{"class":302},"population",[264,929,924],{"class":309},[264,931,932],{"class":302},") \u002F 40, 1)\"",[264,934,935],{"class":273},"),\n",[264,937,938],{"class":154,"line":352},[264,939,530],{"class":273},[264,941,942],{"class":154,"line":370},[264,943,908],{"class":273},[264,945,946],{"class":154,"line":521},[264,947,948],{"class":273},"    QgsSymbolLayer.PropertyFillColor,\n",[264,950,951],{"class":154,"line":533},[264,952,953],{"class":273},"    QgsProperty.fromExpression(\n",[264,955,956,959,961,964,966],{"class":154,"line":539},[264,957,958],{"class":302},"        \"case when ",[264,960,924],{"class":309},[264,962,963],{"class":302},"status",[264,965,924],{"class":309},[264,967,968],{"class":302}," = 'closed' then '#b91c1c' else '#15803d' end\"\n",[264,970,971],{"class":154,"line":545},[264,972,973],{"class":273},"    ),\n",[264,975,976],{"class":154,"line":550},[264,977,530],{"class":273},[14,979,980,383,982,985,986,990,991,994,995,998],{},[380,981,382],{},[220,983,984],{},"QgsProperty.fromExpression()"," compiles a ",[30,987,989],{"href":988},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002F","QGIS expression"," once and evaluates it per feature during rendering. ",[220,992,993],{},"coalesce()"," around the size expression is not decoration — a null population would otherwise produce a null size and QGIS falls back to the static value silently, so half your markers come out the wrong size with no error anywhere. ",[220,996,997],{},"QgsProperty.fromField(\"size_mm\")"," is the cheaper form when the value is already a column and needs no arithmetic.",[14,1000,1001,1002,1005],{},"The property enum names differ between symbol-layer families, and they moved in QGIS 3.36 to a scoped ",[220,1003,1004],{},"QgsSymbolLayer.Property.Size"," form with the old flat names kept as aliases. Code that has to run on both writes the flat name; code pinned to 3.36+ can use the scoped one.",[14,1007,1008],{},[48,1009,1012,1015,1018,1021,1028,1031,1035,1039,1043,1047,1050,1054,1057,1060,1063,1067,1070,1073,1076,1079,1084,1088,1093,1096,1099,1104,1109],{"viewBox":1010,"role":51,"ariaLabel":1011,"xmlns":53},"0 0 760 316","How a data-defined property replaces a static symbol property: for each feature the expression is evaluated against the feature's attributes and the result overrides the stored value for that one draw",[55,1013,1014],{},"Where a data-defined override takes effect",[59,1016,1017],{},"During rendering, each feature reaches the symbol layer. If a property has no override the stored static value is used. If an override is set, the expression is evaluated against that feature and its result is used instead for that feature only, so one symbol produces many appearances.",[63,1019],{"x":65,"y":65,"width":66,"height":1020,"fill":68},"316",[70,1022,1023],{},[73,1024,1026],{"id":1025,"viewBox":76,"refX":77,"refY":78,"markerWidth":79,"markerHeight":79,"orient":80},"ddArrow",[82,1027],{"d":84,"fill":85},[87,1029,1030],{"x":89,"y":90,"style":91,"fill":92,"textAnchor":93},"One symbol, a different answer for every feature",[63,1032],{"x":97,"y":1033,"width":99,"height":1034,"rx":77,"fill":119,"stroke":120,"style":175},"56","86",[87,1036,1038],{"x":106,"y":1037,"style":108,"fill":120,"textAnchor":93},"82","feature",[87,1040,1042],{"x":106,"y":1041,"style":113,"fill":85,"textAnchor":93},"104","population = 82 000",[87,1044,1046],{"x":106,"y":1045,"style":113,"fill":85,"textAnchor":93},"124","status = open",[63,1048],{"x":117,"y":1033,"width":1049,"height":1034,"rx":77,"fill":131,"stroke":132,"style":103},"200",[87,1051,1053],{"x":1052,"y":1037,"style":108,"fill":132,"textAnchor":93},"314","symbol layer property",[87,1055,1056],{"x":1052,"y":1041,"style":113,"fill":85,"textAnchor":93},"size = 3.0 (stored)",[87,1058,1059],{"x":1052,"y":1045,"style":113,"fill":85,"textAnchor":93},"override set? yes",[63,1061],{"x":1062,"y":1033,"width":178,"height":1034,"rx":77,"fill":101,"stroke":102,"style":103},"454",[87,1064,1066],{"x":1065,"y":1037,"style":108,"fill":102,"textAnchor":93},"552","expression evaluated",[87,1068,1069],{"x":1065,"y":1041,"style":113,"fill":85,"textAnchor":93},"sqrt(population) \u002F 40",[87,1071,1072],{"x":1065,"y":1045,"style":113,"fill":85,"textAnchor":93},"= 7.2 mm this feature",[154,1074],{"x1":156,"y1":106,"x2":158,"y2":106,"stroke":85,"style":1075},"stroke-width:2;marker-end:url(#ddArrow)",[154,1077],{"x1":130,"y1":106,"x2":1078,"y2":106,"stroke":85,"style":1075},"448",[63,1080],{"x":1081,"y":182,"width":1082,"height":1083,"rx":173,"fill":174,"stroke":186,"style":175},"120","520","96",[87,1085,1087],{"x":89,"y":1086,"style":108,"fill":92,"textAnchor":93},"202","what the reader sees",[1089,1090],"circle",{"cx":1091,"cy":1092,"r":77,"fill":196,"stroke":120,"style":175},"220","238",[1089,1094],{"cx":648,"cy":1092,"r":1095,"fill":196,"stroke":120,"style":175},"14",[1089,1097],{"cx":673,"cy":1092,"r":1098,"fill":196,"stroke":120,"style":175},"20",[1089,1100],{"cx":1101,"cy":1092,"r":1102,"fill":131,"stroke":1103,"style":175},"510","12","#b91c1c",[87,1105,1108],{"x":1101,"y":1106,"style":1107,"fill":1103,"textAnchor":93},"266","text-anchor:middle;font-size:10px;font-family:sans-serif","closed",[87,1110,1112],{"x":89,"y":1111,"style":113,"fill":186,"textAnchor":93},"302","a null attribute yields a null result and silently falls back to the stored value",[210,1114,1116],{"id":1115},"geometry-generators-drawing-something-the-feature-is-not","Geometry generators: drawing something the feature is not",[14,1118,1119,1120,1123],{},"A ",[220,1121,1122],{},"QgsGeometryGeneratorSymbolLayer"," replaces the feature's geometry with the result of an expression before drawing. The feature stays a polygon in the data; the symbol layer draws its centroid, or its bounding box, or a buffer around it, or a line from it to somewhere else.",[14,1125,1126,1127,1131],{},"This solves problems that otherwise need a whole derived layer: a halo around selected parcels, a leader line from a point to its label anchor, an arrow at the end of every route, a shadow offset a couple of millimetres from every building. Because the generator is a symbol layer like any other, it stacks with the ordinary ones and it obeys the same data-defined overrides. The ",[30,1128,1130],{"href":1129},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fgeometry-generator-symbol-layer-pyqgis\u002F","dedicated geometry generator guide"," covers the expression forms and the performance cliff to avoid.",[210,1133,1135],{"id":1134},"blend-modes-opacity-and-draw-effects","Blend modes, opacity and draw effects",[14,1137,1138,1139,1142,1143,1146,1147,1150],{},"Two symbols overlapping is a compositing question, and QGIS exposes it at three levels: the layer, the symbol, and the individual symbol layer. ",[220,1140,1141],{},"layer.setOpacity(0.6)"," fades the whole rendered layer after it is drawn, which is different from setting every symbol's colour to 60 percent alpha — the former will not let a polygon's own overlapping parts show through itself, the latter will. ",[220,1144,1145],{},"symbol.setOpacity()"," and ",[220,1148,1149],{},"QgsSymbolLayer.setRenderingPass()"," control the same idea further down.",[14,1152,1153,1154,1157,1158,1161,1162,1165,1166,1170],{},"Blend modes turn overlapping fills into something readable: ",[220,1155,1156],{},"Multiply"," darkens where things overlap and is the standard choice for shaded relief under a thematic fill, ",[220,1159,1160],{},"Screen"," lightens, and ",[220,1163,1164],{},"Darken"," keeps whichever is already darker. ",[30,1167,1169],{"href":1168},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fset-layer-opacity-and-blend-mode-pyqgis\u002F","Setting opacity and blend modes from Python"," covers the enum values and the export caveat, which is real: some blend modes cannot be represented in PDF or SVG and QGIS rasterises the layer to keep the appearance, quietly turning a vector export into a partly raster one.",[210,1172,1174],{"id":1173},"placing-markers-along-lines-and-inside-polygons","Placing markers along lines and inside polygons",[14,1176,1177],{},"Three symbol-layer types exist purely to place a sub-symbol somewhere derived from the geometry, and they are the reason a lot of maps look hand-made.",[14,1179,1180,1183,1184,1187,1188,1191],{},[220,1181,1182],{},"QgsMarkerLineSymbolLayer"," repeats a marker along a line at an interval, or places one at the first vertex, the last vertex, every vertex, or the curve point. Arrowheads on a route, ticks on a contour, chevrons on a one-way street and the dots on a ferry line are all this one class with a different placement. ",[220,1185,1186],{},"QgsPointPatternFillSymbolLayer"," tiles a marker across a polygon on a regular grid — the standard treatment for marsh, orchard and quarry fills. ",[220,1189,1190],{},"QgsCentroidFillSymbolLayer"," places a single marker at the polygon's centroid, which is how a polygon layer gets a point-like label anchor without a second layer.",[255,1193,1195],{"className":257,"code":1194,"language":259,"meta":260,"style":260},"from qgis.core import (\n    QgsLineSymbol, QgsMarkerLineSymbolLayer, QgsMarkerSymbol,\n    QgsSimpleMarkerSymbolLayer, QgsSimpleMarkerSymbolLayerBase,\n)\nfrom qgis.PyQt.QtGui import QColor\n\narrow = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayerBase.Triangle)\narrow.setColor(QColor(\"#0f766e\"))\narrow.setSize(3.0)\narrow.setAngle(90)\n\nmarker_line = QgsMarkerLineSymbolLayer()\nmarker_line.setPlacement(QgsMarkerLineSymbolLayer.Interval)\nmarker_line.setInterval(8.0)\nmarker_line.setRotateMarker(True)\nmarker_line.setSubSymbol(QgsMarkerSymbol([arrow]))\n\nsymbol = QgsLineSymbol()\nsymbol.appendSymbolLayer(marker_line)\n",[220,1196,1197,1208,1213,1218,1222,1232,1236,1246,1256,1266,1276,1280,1290,1295,1305,1315,1320,1324,1332],{"__ignoreMap":260},[264,1198,1199,1201,1203,1205],{"class":154,"line":266},[264,1200,270],{"class":269},[264,1202,274],{"class":273},[264,1204,277],{"class":269},[264,1206,1207],{"class":273}," (\n",[264,1209,1210],{"class":154,"line":283},[264,1211,1212],{"class":273},"    QgsLineSymbol, QgsMarkerLineSymbolLayer, QgsMarkerSymbol,\n",[264,1214,1215],{"class":154,"line":290},[264,1216,1217],{"class":273},"    QgsSimpleMarkerSymbolLayer, QgsSimpleMarkerSymbolLayerBase,\n",[264,1219,1220],{"class":154,"line":315},[264,1221,530],{"class":273},[264,1223,1224,1226,1228,1230],{"class":154,"line":326},[264,1225,270],{"class":269},[264,1227,454],{"class":273},[264,1229,277],{"class":269},[264,1231,459],{"class":273},[264,1233,1234],{"class":154,"line":331},[264,1235,287],{"emptyLinePlaceholder":286},[264,1237,1238,1241,1243],{"class":154,"line":352},[264,1239,1240],{"class":273},"arrow ",[264,1242,296],{"class":269},[264,1244,1245],{"class":273}," QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayerBase.Triangle)\n",[264,1247,1248,1251,1254],{"class":154,"line":370},[264,1249,1250],{"class":273},"arrow.setColor(QColor(",[264,1252,1253],{"class":302},"\"#0f766e\"",[264,1255,518],{"class":273},[264,1257,1258,1261,1264],{"class":154,"line":521},[264,1259,1260],{"class":273},"arrow.setSize(",[264,1262,1263],{"class":309},"3.0",[264,1265,530],{"class":273},[264,1267,1268,1271,1274],{"class":154,"line":533},[264,1269,1270],{"class":273},"arrow.setAngle(",[264,1272,1273],{"class":309},"90",[264,1275,530],{"class":273},[264,1277,1278],{"class":154,"line":539},[264,1279,287],{"emptyLinePlaceholder":286},[264,1281,1282,1285,1287],{"class":154,"line":545},[264,1283,1284],{"class":273},"marker_line ",[264,1286,296],{"class":269},[264,1288,1289],{"class":273}," QgsMarkerLineSymbolLayer()\n",[264,1291,1292],{"class":154,"line":550},[264,1293,1294],{"class":273},"marker_line.setPlacement(QgsMarkerLineSymbolLayer.Interval)\n",[264,1296,1297,1300,1303],{"class":154,"line":565},[264,1298,1299],{"class":273},"marker_line.setInterval(",[264,1301,1302],{"class":309},"8.0",[264,1304,530],{"class":273},[264,1306,1307,1310,1313],{"class":154,"line":576},[264,1308,1309],{"class":273},"marker_line.setRotateMarker(",[264,1311,1312],{"class":309},"True",[264,1314,530],{"class":273},[264,1316,1317],{"class":154,"line":582},[264,1318,1319],{"class":273},"marker_line.setSubSymbol(QgsMarkerSymbol([arrow]))\n",[264,1321,1322],{"class":154,"line":588},[264,1323,287],{"emptyLinePlaceholder":286},[264,1325,1326,1328,1330],{"class":154,"line":593},[264,1327,318],{"class":273},[264,1329,296],{"class":269},[264,1331,484],{"class":273},[264,1333,1334],{"class":154,"line":599},[264,1335,1336],{"class":273},"symbol.appendSymbolLayer(marker_line)\n",[14,1338,1339,383,1341,1344,1345,1348,1349,1352,1353,1356],{},[380,1340,382],{},[220,1342,1343],{},"setSubSymbol()"," takes a whole symbol, not a symbol layer — which is why the triangle is wrapped in ",[220,1346,1347],{},"QgsMarkerSymbol([arrow])",". That nesting is the part people trip over, and it is also what makes the class powerful: the sub-symbol can itself be a stack, so an arrow with a casing is possible. ",[220,1350,1351],{},"setRotateMarker(True)"," aligns each marker to the local direction of the line, without which every triangle points the same way regardless of where the road goes. ",[220,1354,1355],{},"setInterval()"," is in the symbol layer's output unit, so switching to map units makes the spacing a real-world distance instead of a paper one.",[14,1358,1359,1360,226,1363,226,1366,226,1369,226,1372,1146,1375,1378,1379,701,1381,1383],{},"Placement is an enum worth knowing by name: ",[220,1361,1362],{},"Interval",[220,1364,1365],{},"Vertex",[220,1367,1368],{},"FirstVertex",[220,1370,1371],{},"LastVertex",[220,1373,1374],{},"CentralPoint",[220,1376,1377],{},"CurvePoint",". A single arrowhead at the end of every route is ",[220,1380,1371],{},[220,1382,1351],{},", and it needs no interval at all.",[210,1385,1387],{"id":1386},"scale-dependent-symbology","Scale-dependent symbology",[14,1389,1390],{},"A symbol that works at 1:5 000 rarely works at 1:250 000, and QGIS gives you three ways to handle that without maintaining two layers.",[14,1392,1393,1394,1398,1399,1402],{},"The blunt instrument is ",[30,1395,1397],{"href":1396},"\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fset-scale-based-visibility-pyqgis\u002F","scale-based visibility"," on the layer, which simply stops drawing it outside a range. Finer than that, a rule-based renderer attaches a scale range to each rule, so the same layer can draw thin casings when zoomed in and plain lines when zoomed out. Finest of all, a data-defined override can read the current scale directly, because ",[220,1400,1401],{},"@map_scale"," is available inside the expression during rendering:",[255,1404,1406],{"className":257,"code":1405,"language":259,"meta":260,"style":260},"from qgis.core import QgsProperty, QgsSymbolLayer\n\nline = symbol.symbolLayer(0)\nline.setDataDefinedProperty(\n    QgsSymbolLayer.PropertyStrokeWidth,\n    QgsProperty.fromExpression(\"scale_linear(@map_scale, 5000, 250000, 1.6, 0.3)\"),\n)\n",[220,1407,1408,1418,1422,1435,1440,1445,1454],{"__ignoreMap":260},[264,1409,1410,1412,1414,1416],{"class":154,"line":266},[264,1411,270],{"class":269},[264,1413,274],{"class":273},[264,1415,277],{"class":269},[264,1417,885],{"class":273},[264,1419,1420],{"class":154,"line":283},[264,1421,287],{"emptyLinePlaceholder":286},[264,1423,1424,1427,1429,1431,1433],{"class":154,"line":290},[264,1425,1426],{"class":273},"line ",[264,1428,296],{"class":269},[264,1430,899],{"class":273},[264,1432,65],{"class":309},[264,1434,530],{"class":273},[264,1436,1437],{"class":154,"line":315},[264,1438,1439],{"class":273},"line.setDataDefinedProperty(\n",[264,1441,1442],{"class":154,"line":326},[264,1443,1444],{"class":273},"    QgsSymbolLayer.PropertyStrokeWidth,\n",[264,1446,1447,1449,1452],{"class":154,"line":331},[264,1448,918],{"class":273},[264,1450,1451],{"class":302},"\"scale_linear(@map_scale, 5000, 250000, 1.6, 0.3)\"",[264,1453,935],{"class":273},[264,1455,1456],{"class":154,"line":352},[264,1457,530],{"class":273},[14,1459,1460,383,1462,1465],{},[380,1461,382],{},[220,1463,1464],{},"scale_linear()"," maps the current map scale onto a width range and clamps at both ends, so the line tapers smoothly as the map zooms out rather than jumping at a rule boundary. Because the expression re-evaluates on every repaint, this costs one expression evaluation per feature per redraw — cheap for a few thousand features, noticeable for a few hundred thousand, and a good reason to prefer rule-based scale ranges on large layers.",[210,1467,1469],{"id":1468},"what-makes-symbology-slow","What makes symbology slow",[14,1471,1472],{},"Rendering cost scales with the number of symbol layers multiplied by the number of features, and a few choices dominate.",[14,1474,1475],{},"SVG markers are re-parsed and re-rasterised per unique size and colour combination; a data-defined colour on an SVG marker across fifty thousand points is the single most reliable way to make a canvas unusable. Font markers are much cheaper because the glyph is cached. Geometry generators run an expression per feature per draw, and a generator that buffers is doing real geometry work at every repaint. Pattern fills at small line spacings generate enormous numbers of primitives.",[14,1477,1478,1479,1483],{},"The practical rules: keep the stack as short as the design allows, prefer simple markers where the shape is a shape, cache what does not change by baking derived geometry into a real field instead of a generator, and check the effect with the ",[30,1480,1482],{"href":1481},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fprofile-slow-pyqgis-code\u002F","profiling techniques"," rather than guessing which layer is expensive.",[210,1485,1487],{"id":1486},"key-takeaways","Key takeaways",[1489,1490,1491,1495,1498,1505,1515,1518,1521],"ul",{},[1492,1493,1494],"li",{},"A symbol is an ordered stack of symbol layers painted from index 0 upward; the casing must be added before the fill, not after.",[1492,1496,1497],{},"A freshly built symbol already holds one default layer — delete it before appending, or it shows through at the joins.",[1492,1499,1500,1501,1504],{},"Property dictionaries from ",[220,1502,1503],{},"symbolLayer.properties()"," are the fastest bridge between a style you built in the GUI and a script that reproduces it.",[1492,1506,1507,1508,1511,1512,1514],{},"Any symbol-layer property can be replaced with a ",[220,1509,1510],{},"QgsProperty"," expression; wrap numeric ones in ",[220,1513,993],{}," so nulls do not silently fall back.",[1492,1516,1517],{},"Geometry generators draw something derived from the feature without creating a derived layer, at the cost of an expression per feature per repaint.",[1492,1519,1520],{},"Blend modes are set per layer, per symbol or per symbol layer, and some of them force rasterisation on PDF and SVG export.",[1492,1522,1523,1524,1526],{},"Every symbology change needs ",[220,1525,419],{},", and a legend refresh if the change alters what the legend should show.",[210,1528,1530],{"id":1529},"frequently-asked-questions","Frequently Asked Questions",[14,1532,1533,1536,1539,1540,1542,1543,1546],{},[380,1534,1535],{},"Why does my new symbol still show a blue line underneath it?",[220,1537,1538],{},"QgsLineSymbol()"," and its siblings are constructed with one default symbol layer already present. Call ",[220,1541,625],{}," before appending yours, or build the symbol with ",[220,1544,1545],{},"QgsLineSymbol.createSimple({...})"," and edit layer 0 in place instead of appending.",[14,1548,1549,1552,1553,1556,1557,1146,1559,1562],{},[380,1550,1551],{},"How do I find the property keys for a symbol layer type?","\nStyle one layer the way you want in the GUI, then in the Python console run ",[220,1554,1555],{},"layer.renderer().symbol().symbolLayer(0).properties()",". The dictionary it returns is exactly what ",[220,1558,839],{},[220,1560,1561],{},"QgsSymbolLayerRegistry"," accept, so there is no need to look the keys up.",[14,1564,1565,1568,1569,1572],{},[380,1566,1567],{},"Can a categorized renderer use a stacked symbol?","\nYes. Each category holds a full symbol, so build the stack once and clone it per category with ",[220,1570,1571],{},"symbol.clone()",", changing only the colour of the layer that should vary. Cloning matters — assigning the same symbol object to several categories makes them share state.",[14,1574,1575,1578],{},[380,1576,1577],{},"What is the difference between layer opacity and symbol opacity?","\nLayer opacity is applied to the finished raster of the whole layer, so a polygon overlapping itself still looks solid. Symbol or colour alpha is applied per drawn primitive, so overlaps accumulate and read darker. Choose layer opacity for a wash over a basemap and colour alpha when the overlap itself is the information.",[14,1580,1581,1584,1585,1588,1589,1593],{},[380,1582,1583],{},"Do symbol changes need the layer to be in edit mode?","\nNo. Symbology lives in the layer's style, not in the data, so no edit session or commit is involved. Persist it with ",[220,1586,1587],{},"layer.saveNamedStyle()"," or by ",[30,1590,1592],{"href":1591},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis\u002F","saving a QML",", and remember that saving the project stores the style inside the project file too.",[14,1595,1596,1599,1600,1146,1603,1606,1607,424],{},[380,1597,1598],{},"Why did my SVG marker come out black?","\nThe SVG does not use the QGIS parameter convention, so ",[220,1601,1602],{},"param(fill)",[220,1604,1605],{},"param(outline)"," are absent and QGIS cannot recolour it. Either edit the SVG to use those placeholders or accept the file's own colours and stop setting ",[220,1608,1609],{},"setFillColor()",[210,1611,1613],{"id":1612},"related","Related",[1489,1615,1616,1621,1626,1632,1637,1642,1648,1653],{},[1492,1617,1618,1620],{},[30,1619,33],{"href":32}," — the section this topic belongs to",[1492,1622,1623],{},[30,1624,1625],{"href":708},"Stack Symbol Layers in PyQGIS",[1492,1627,1628],{},[30,1629,1631],{"href":1630},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fuse-svg-marker-symbol-pyqgis\u002F","Use SVG and Font Markers in PyQGIS",[1492,1633,1634],{},[30,1635,1636],{"href":1129},"Draw with a Geometry Generator in PyQGIS",[1492,1638,1639],{},[30,1640,1641],{"href":1168},"Set Layer Opacity and Blend Modes in PyQGIS",[1492,1643,1644],{},[30,1645,1647],{"href":1646},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fcreate-heatmap-renderer-pyqgis\u002F","Build a Heatmap Renderer in PyQGIS",[1492,1649,1650],{},[30,1651,1652],{"href":37},"Programmatic Layer Styling in PyQGIS",[1492,1654,1655],{},[30,1656,1657],{"href":42},"Graduated & Categorized Renderers in PyQGIS",[1659,1660,1661],"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":260,"searchDepth":283,"depth":283,"links":1663},[1664,1665,1666,1667,1668,1669,1670,1671,1672,1673,1674,1675],{"id":212,"depth":283,"text":213},{"id":427,"depth":283,"text":428},{"id":713,"depth":283,"text":714},{"id":865,"depth":283,"text":866},{"id":1115,"depth":283,"text":1116},{"id":1134,"depth":283,"text":1135},{"id":1173,"depth":283,"text":1174},{"id":1386,"depth":283,"text":1387},{"id":1468,"depth":283,"text":1469},{"id":1486,"depth":283,"text":1487},{"id":1529,"depth":283,"text":1530},{"id":1612,"depth":283,"text":1613},"Build symbols from stacked symbol layers in PyQGIS — casings, SVG and font markers, data-defined overrides, geometry generators, blend modes and the draw order that decides what wins.","md",{"slug":1679,"type":1680,"breadcrumb":1681,"datePublished":1682,"dateModified":1682},"symbol-layers-and-advanced-symbology","guide","Symbol Layers & Advanced Symbology","2026-08-27","\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology",{"title":5,"description":1676},"pyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Findex","eEAO23AJ45aGbydJUSk0mSf00dPK_tD3BGgshhWaJLA",1787823360560]