[{"data":1,"prerenderedAt":2255},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-custom-symbol-layer-pyqgis":3},{"id":4,"title":5,"body":6,"description":2244,"extension":2245,"meta":2246,"navigation":309,"path":2251,"seo":2252,"stem":2253,"__hash__":2254},"docs\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-custom-symbol-layer-pyqgis\u002Findex.md","Write a Custom Symbol Layer in PyQGIS",{"type":7,"value":8,"toc":2230},"minimark",[9,13,17,35,182,187,226,230,237,693,734,738,747,830,1223,1245,1249,1252,1468,1484,1488,1491,1588,1601,1605,1616,1704,2021,2039,2043,2052,2056,2086,2090,2116,2120,2141,2145,2166,2172,2181,2191,2195,2226],[10,11,5],"h1",{"id":12},"write-a-custom-symbol-layer-in-pyqgis",[14,15,16],"p",{},"QGIS's symbol layers — simple markers, SVG markers, font markers, geometry generators — cover an enormous range of cartography. Occasionally a map needs a mark they cannot draw: a wind barb computed from two attributes, a domain-specific glyph such as a geological dip symbol, a tiny sparkline at each station. A custom symbol layer written in Python plugs into the same system as the built-in ones: it appears in the symbol selector, stacks with other layers, follows data-defined size and rotation, and is drawn by the normal renderer.",[14,18,19,20,25,26,30,31,34],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002F","Extending QGIS with Custom Classes",". It subclasses ",[27,28,29],"code",{},"QgsMarkerSymbolLayer",", draws with ",[27,32,33],{},"QPainter"," in the right units, stores properties so styles survive saving, registers the class from a plugin, and covers performance and the limits of Python symbol layers.",[14,36,37],{},[38,39,44,48,52,59,76,85,94,100,105,109,113,121,123,131,137,142,146,152,156,160,165,171,175,178],"svg",{"viewBox":40,"role":41,"ariaLabel":42,"xmlns":43},"0 0 760 264","img","A symbol made of built-in and custom symbol layers, the registry creating layers from saved properties, and renderPoint drawing each feature with QPainter","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[45,46,47],"title",{},"Where a custom symbol layer fits",[49,50,51],"desc",{},"A symbol is a stack of symbol layers. Built-in layers and a custom Python marker layer sit side by side in the stack. The symbol layer registry knows each type by name through a metadata object, which creates instances from saved properties. At render time the renderer asks the symbol to draw each feature, and each layer's renderPoint receives the point in painter coordinates and a render context with the painter and unit conversions.",[53,54],"rect",{"x":55,"y":55,"width":56,"height":57,"fill":58},"0","760","264","#f6f3ea",[60,61,62],"defs",{},[63,64,71],"marker",{"id":65,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"slArchArrow","0 0 10 10","8","5","7","auto-start-reverse",[72,73],"path",{"d":74,"fill":75},"M0 0 L10 5 L0 10 z","#2f3b35",[77,78,84],"text",{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","One more layer in the symbol stack",[53,86],{"x":87,"y":88,"width":89,"height":90,"rx":67,"fill":91,"stroke":92,"style":93},"24","52","230","196","#fffdf7","#59645f","stroke-width:2",[77,95,99],{"x":96,"y":97,"style":98,"fill":82,"textAnchor":83},"139","102.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","QgsMarkerSymbol",[77,101,104],{"x":96,"y":102,"style":103,"fill":75,"textAnchor":83},"136.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","simple marker",[77,106,108],{"x":96,"y":107,"style":103,"fill":75,"textAnchor":83},"170.78","your Python layer",[77,110,112],{"x":96,"y":111,"style":103,"fill":75,"textAnchor":83},"204.78","SVG marker",[114,115],"line",{"x1":116,"y1":117,"x2":118,"y2":119,"stroke":75,"style":120},"254","150","296","110","stroke-width:1.8;marker-end:url(#slArchArrow)",[114,122],{"x1":116,"y1":117,"x2":118,"y2":90,"stroke":75,"style":120},[53,124],{"x":125,"y":126,"width":127,"height":128,"rx":67,"fill":129,"stroke":130,"style":93},"300","66","210","88","#eef7f4","#0f766e",[77,132,136],{"x":133,"y":134,"style":135,"fill":130,"textAnchor":83},"405","91.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","symbol layer registry",[77,138,141],{"x":133,"y":139,"style":140,"fill":92,"textAnchor":83},"113.6","text-anchor:middle;font-size:10.0px;font-family:sans-serif","metadata: name →",[77,143,145],{"x":133,"y":144,"style":140,"fill":92,"textAnchor":83},"135.6","createSymbolLayer()",[53,147],{"x":125,"y":148,"width":127,"height":149,"rx":67,"fill":150,"stroke":151,"style":93},"168","80","#eff3ff","#2563eb",[77,153,155],{"x":133,"y":154,"style":98,"fill":151,"textAnchor":83},"199.78","renderPoint()",[77,157,159],{"x":133,"y":158,"style":103,"fill":92,"textAnchor":83},"223.78","QPainter + context",[114,161],{"x1":162,"y1":163,"x2":164,"y2":163,"stroke":75,"style":120},"510","208","552",[53,166],{"x":167,"y":148,"width":168,"height":149,"rx":67,"fill":169,"stroke":170,"style":93},"556","180","#e8efe6","#15803d",[77,172,174],{"x":173,"y":154,"style":103,"fill":75,"textAnchor":83},"646","map canvas,",[77,176,177],{"x":173,"y":158,"style":103,"fill":75,"textAnchor":83},"layouts, exports",[77,179,181],{"x":180,"y":119,"style":140,"fill":92,"textAnchor":83},"618","styles save via properties()",[183,184,186],"h2",{"id":185},"prerequisites","Prerequisites",[188,189,190,194,206],"ul",{},[191,192,193],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[191,195,196,197,200,201,205],{},"A plugin to host the code. Symbol layer types must be registered every time QGIS starts, which a plugin's ",[27,198,199],{},"initGui"," (or a startup script) does; see ",[21,202,204],{"href":203},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder\u002F","creating a plugin with Plugin Builder",".",[191,207,208,209,211,212,215,216,215,219,222,223,205],{},"Basic ",[27,210,33],{}," knowledge: pens, brushes, ",[27,213,214],{},"drawLine",", ",[27,217,218],{},"drawEllipse",[27,220,221],{},"save"," and ",[27,224,225],{},"restore",[183,227,229],{"id":228},"subclass-qgsmarkersymbollayer","Subclass QgsMarkerSymbolLayer",[14,231,232,233,236],{},"A marker symbol layer needs a type name, a way to describe its properties, a clone method, and ",[27,234,235],{},"renderPoint",". The example draws a simple wind barb: a staff pointing into the wind with one feather per 10 knots.",[238,239,244],"pre",{"className":240,"code":241,"language":242,"meta":243,"style":243},"language-python shiki shiki-themes github-dark","import math\nfrom qgis.core import (QgsMarkerSymbolLayer, QgsSymbolLayerUtils, QgsUnitTypes,\n                       QgsRenderContext)\nfrom qgis.PyQt.QtCore import QPointF\nfrom qgis.PyQt.QtGui import QColor, QPen\n\nclass WindBarbSymbolLayer(QgsMarkerSymbolLayer):\n    TYPE = \"PyWindBarb\"\n\n    def __init__(self, color=QColor(\"#17211d\"), size=8.0, speed_field=\"speed_kn\"):\n        super().__init__()\n        self.setColor(color)\n        self.setSize(size)                        # staff length, millimetres\n        self.speed_field = speed_field\n        self._pen = None\n\n    def layerType(self):\n        return self.TYPE\n\n    def properties(self):\n        return {\"color\": QgsSymbolLayerUtils.encodeColor(self.color()),\n                \"size\": str(self.size()),\n                \"speed_field\": self.speed_field}\n\n    @staticmethod\n    def create(props):\n        return WindBarbSymbolLayer(\n            QgsSymbolLayerUtils.decodeColor(props.get(\"color\", \"#17211d\")),\n            float(props.get(\"size\", 8.0)),\n            props.get(\"speed_field\", \"speed_kn\"))\n\n    def clone(self):\n        c = WindBarbSymbolLayer(self.color(), self.size(), self.speed_field)\n        self.copyDataDefinedProperties(c)\n        self.copyPaintEffect(c)\n        return c\n","python","",[27,245,246,258,272,278,291,304,311,329,343,348,387,402,411,423,436,449,454,465,479,484,494,514,533,546,551,560,571,579,594,612,628,633,643,669,677,685],{"__ignoreMap":243},[247,248,250,254],"span",{"class":114,"line":249},1,[247,251,253],{"class":252},"snl16","import",[247,255,257],{"class":256},"s95oV"," math\n",[247,259,261,264,267,269],{"class":114,"line":260},2,[247,262,263],{"class":252},"from",[247,265,266],{"class":256}," qgis.core ",[247,268,253],{"class":252},[247,270,271],{"class":256}," (QgsMarkerSymbolLayer, QgsSymbolLayerUtils, QgsUnitTypes,\n",[247,273,275],{"class":114,"line":274},3,[247,276,277],{"class":256},"                       QgsRenderContext)\n",[247,279,281,283,286,288],{"class":114,"line":280},4,[247,282,263],{"class":252},[247,284,285],{"class":256}," qgis.PyQt.QtCore ",[247,287,253],{"class":252},[247,289,290],{"class":256}," QPointF\n",[247,292,294,296,299,301],{"class":114,"line":293},5,[247,295,263],{"class":252},[247,297,298],{"class":256}," qgis.PyQt.QtGui ",[247,300,253],{"class":252},[247,302,303],{"class":256}," QColor, QPen\n",[247,305,307],{"class":114,"line":306},6,[247,308,310],{"emptyLinePlaceholder":309},true,"\n",[247,312,314,317,321,324,326],{"class":114,"line":313},7,[247,315,316],{"class":252},"class",[247,318,320],{"class":319},"svObZ"," WindBarbSymbolLayer",[247,322,323],{"class":256},"(",[247,325,29],{"class":319},[247,327,328],{"class":256},"):\n",[247,330,332,336,339],{"class":114,"line":331},8,[247,333,335],{"class":334},"sDLfK","    TYPE",[247,337,338],{"class":252}," =",[247,340,342],{"class":341},"sU2Wk"," \"PyWindBarb\"\n",[247,344,346],{"class":114,"line":345},9,[247,347,310],{"emptyLinePlaceholder":309},[247,349,351,354,357,360,363,366,369,372,374,377,380,382,385],{"class":114,"line":350},10,[247,352,353],{"class":252},"    def",[247,355,356],{"class":334}," __init__",[247,358,359],{"class":256},"(self, color",[247,361,362],{"class":252},"=",[247,364,365],{"class":256},"QColor(",[247,367,368],{"class":341},"\"#17211d\"",[247,370,371],{"class":256},"), size",[247,373,362],{"class":252},[247,375,376],{"class":334},"8.0",[247,378,379],{"class":256},", speed_field",[247,381,362],{"class":252},[247,383,384],{"class":341},"\"speed_kn\"",[247,386,328],{"class":256},[247,388,390,393,396,399],{"class":114,"line":389},11,[247,391,392],{"class":334},"        super",[247,394,395],{"class":256},"().",[247,397,398],{"class":334},"__init__",[247,400,401],{"class":256},"()\n",[247,403,405,408],{"class":114,"line":404},12,[247,406,407],{"class":334},"        self",[247,409,410],{"class":256},".setColor(color)\n",[247,412,414,416,419],{"class":114,"line":413},13,[247,415,407],{"class":334},[247,417,418],{"class":256},".setSize(size)                        ",[247,420,422],{"class":421},"sjoCn","# staff length, millimetres\n",[247,424,426,428,431,433],{"class":114,"line":425},14,[247,427,407],{"class":334},[247,429,430],{"class":256},".speed_field ",[247,432,362],{"class":252},[247,434,435],{"class":256}," speed_field\n",[247,437,439,441,444,446],{"class":114,"line":438},15,[247,440,407],{"class":334},[247,442,443],{"class":256},"._pen ",[247,445,362],{"class":252},[247,447,448],{"class":334}," None\n",[247,450,452],{"class":114,"line":451},16,[247,453,310],{"emptyLinePlaceholder":309},[247,455,457,459,462],{"class":114,"line":456},17,[247,458,353],{"class":252},[247,460,461],{"class":319}," layerType",[247,463,464],{"class":256},"(self):\n",[247,466,468,471,474,476],{"class":114,"line":467},18,[247,469,470],{"class":252},"        return",[247,472,473],{"class":334}," self",[247,475,205],{"class":256},[247,477,478],{"class":334},"TYPE\n",[247,480,482],{"class":114,"line":481},19,[247,483,310],{"emptyLinePlaceholder":309},[247,485,487,489,492],{"class":114,"line":486},20,[247,488,353],{"class":252},[247,490,491],{"class":319}," properties",[247,493,464],{"class":256},[247,495,497,499,502,505,508,511],{"class":114,"line":496},21,[247,498,470],{"class":252},[247,500,501],{"class":256}," {",[247,503,504],{"class":341},"\"color\"",[247,506,507],{"class":256},": QgsSymbolLayerUtils.encodeColor(",[247,509,510],{"class":334},"self",[247,512,513],{"class":256},".color()),\n",[247,515,517,520,523,526,528,530],{"class":114,"line":516},22,[247,518,519],{"class":341},"                \"size\"",[247,521,522],{"class":256},": ",[247,524,525],{"class":334},"str",[247,527,323],{"class":256},[247,529,510],{"class":334},[247,531,532],{"class":256},".size()),\n",[247,534,536,539,541,543],{"class":114,"line":535},23,[247,537,538],{"class":341},"                \"speed_field\"",[247,540,522],{"class":256},[247,542,510],{"class":334},[247,544,545],{"class":256},".speed_field}\n",[247,547,549],{"class":114,"line":548},24,[247,550,310],{"emptyLinePlaceholder":309},[247,552,554,557],{"class":114,"line":553},25,[247,555,556],{"class":319},"    @",[247,558,559],{"class":334},"staticmethod\n",[247,561,563,565,568],{"class":114,"line":562},26,[247,564,353],{"class":252},[247,566,567],{"class":319}," create",[247,569,570],{"class":256},"(props):\n",[247,572,574,576],{"class":114,"line":573},27,[247,575,470],{"class":252},[247,577,578],{"class":256}," WindBarbSymbolLayer(\n",[247,580,582,585,587,589,591],{"class":114,"line":581},28,[247,583,584],{"class":256},"            QgsSymbolLayerUtils.decodeColor(props.get(",[247,586,504],{"class":341},[247,588,215],{"class":256},[247,590,368],{"class":341},[247,592,593],{"class":256},")),\n",[247,595,597,600,603,606,608,610],{"class":114,"line":596},29,[247,598,599],{"class":334},"            float",[247,601,602],{"class":256},"(props.get(",[247,604,605],{"class":341},"\"size\"",[247,607,215],{"class":256},[247,609,376],{"class":334},[247,611,593],{"class":256},[247,613,615,618,621,623,625],{"class":114,"line":614},30,[247,616,617],{"class":256},"            props.get(",[247,619,620],{"class":341},"\"speed_field\"",[247,622,215],{"class":256},[247,624,384],{"class":341},[247,626,627],{"class":256},"))\n",[247,629,631],{"class":114,"line":630},31,[247,632,310],{"emptyLinePlaceholder":309},[247,634,636,638,641],{"class":114,"line":635},32,[247,637,353],{"class":252},[247,639,640],{"class":319}," clone",[247,642,464],{"class":256},[247,644,646,649,651,654,656,659,661,664,666],{"class":114,"line":645},33,[247,647,648],{"class":256},"        c ",[247,650,362],{"class":252},[247,652,653],{"class":256}," WindBarbSymbolLayer(",[247,655,510],{"class":334},[247,657,658],{"class":256},".color(), ",[247,660,510],{"class":334},[247,662,663],{"class":256},".size(), ",[247,665,510],{"class":334},[247,667,668],{"class":256},".speed_field)\n",[247,670,672,674],{"class":114,"line":671},34,[247,673,407],{"class":334},[247,675,676],{"class":256},".copyDataDefinedProperties(c)\n",[247,678,680,682],{"class":114,"line":679},35,[247,681,407],{"class":334},[247,683,684],{"class":256},".copyPaintEffect(c)\n",[247,686,688,690],{"class":114,"line":687},36,[247,689,470],{"class":252},[247,691,692],{"class":256}," c\n",[14,694,695,699,700,703,704,707,708,711,712,222,715,718,719,722,723,222,726,729,730,733],{},[696,697,698],"strong",{},"Breakdown:"," ",[27,701,702],{},"layerType()"," returns the name the registry uses to find the class, so it must be unique — a prefix such as ",[27,705,706],{},"Py"," avoids clashes with built-in types. ",[27,709,710],{},"properties()"," returns everything needed to recreate the layer as strings; QGIS writes this dictionary into ",[27,713,714],{},".qgs",[27,716,717],{},".qml"," files, and ",[27,720,721],{},"create()"," reads it back. ",[27,724,725],{},"QgsSymbolLayerUtils.encodeColor",[27,727,728],{},"decodeColor"," handle colours the same way built-in layers do. ",[27,731,732],{},"clone()"," must copy data-defined properties and paint effects explicitly, or a duplicated symbol silently loses them.",[183,735,737],{"id":736},"draw-in-renderpoint","Draw in renderPoint",[14,739,740,742,743,746],{},[27,741,235],{}," receives the feature's position already converted to painter coordinates, plus a symbol render context giving access to the painter, the feature and unit conversions. Expensive setup belongs in ",[27,744,745],{},"startRender",", which runs once per render rather than once per feature.",[14,748,749],{},[38,750,753,756,759,762,769,772,776,781,785,789,795,799,801,804,807,811,816,820,823,826],{"viewBox":751,"role":41,"ariaLabel":752,"xmlns":43},"0 0 760 250","The symbol layer render cycle: startRender once, renderPoint per feature with unit conversion, stopRender once",[45,754,755],{},"The render cycle",[49,757,758],{},"startRender runs once before drawing a layer and prepares pens and field indexes. renderPoint runs for every feature with the point in painter pixels and the feature available from the context. stopRender runs once afterwards to release resources. Sizes are converted from millimetres to pixels with the render context so output is correct on screen and in high resolution exports.",[53,760],{"x":55,"y":55,"width":56,"height":761,"fill":58},"250",[60,763,764],{},[63,765,767],{"id":766,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"slRenderArrow",[72,768],{"d":74,"fill":75},[77,770,771],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Prepare once, draw many, clean up once",[53,773],{"x":87,"y":774,"width":775,"height":119,"rx":67,"fill":129,"stroke":130,"style":93},"70","200",[77,777,780],{"x":778,"y":779,"style":98,"fill":130,"textAnchor":83},"124","104.78","startRender()",[77,782,784],{"x":778,"y":783,"style":103,"fill":92,"textAnchor":83},"128.78","pens, field index",[77,786,788],{"x":778,"y":787,"style":103,"fill":92,"textAnchor":83},"152.78","once per render",[114,790],{"x1":791,"y1":792,"x2":793,"y2":792,"stroke":75,"style":794},"224","125","266","stroke-width:1.8;marker-end:url(#slRenderArrow)",[53,796],{"x":797,"y":774,"width":798,"height":119,"rx":67,"fill":150,"stroke":151,"style":93},"270","220",[77,800,155],{"x":79,"y":779,"style":98,"fill":151,"textAnchor":83},[77,802,803],{"x":79,"y":783,"style":103,"fill":92,"textAnchor":83},"per feature",[77,805,806],{"x":79,"y":787,"style":103,"fill":92,"textAnchor":83},"mm → pixels",[114,808],{"x1":809,"y1":792,"x2":810,"y2":792,"stroke":75,"style":794},"490","532",[53,812],{"x":813,"y":774,"width":775,"height":119,"rx":67,"fill":814,"stroke":815,"style":93},"536","#fdf2e2","#b45309",[77,817,819],{"x":818,"y":779,"style":98,"fill":815,"textAnchor":83},"636","stopRender()",[77,821,822],{"x":818,"y":783,"style":103,"fill":92,"textAnchor":83},"release",[77,824,825],{"x":818,"y":787,"style":103,"fill":92,"textAnchor":83},"once",[77,827,829],{"x":79,"y":828,"style":103,"fill":92,"textAnchor":83},"222","keep renderPoint free of allocations and lookups",[238,831,833],{"className":240,"code":832,"language":242,"meta":243,"style":243},"    def startRender(self, context):\n        self._pen = QPen(self.color())\n        self._pen.setCapStyle(1)                 # Qt.FlatCap\n        rc = context.renderContext()\n        self._pen.setWidthF(rc.convertToPainterUnits(0.3, QgsUnitTypes.RenderMillimeters))\n        self._staff = rc.convertToPainterUnits(self.size(), QgsUnitTypes.RenderMillimeters)\n\n    def stopRender(self, context):\n        self._pen = None\n\n    def renderPoint(self, point, context):\n        feature = context.feature()\n        if feature is None:\n            return\n        speed = feature[self.speed_field] or 0\n        direction = feature[\"dir_deg\"] or 0\n        painter = context.renderContext().painter()\n        painter.save()\n        painter.setPen(self._pen)\n        painter.translate(point)\n        painter.rotate(direction)                # 0 = wind from north\n        painter.drawLine(QPointF(0, 0), QPointF(0, -self._staff))\n        feathers = int(round(speed \u002F 10))\n        step = self._staff \u002F 6\n        for i in range(min(feathers, 5)):\n            y = -self._staff + i * step\n            painter.drawLine(QPointF(0, y), QPointF(self._staff * 0.35, y - step * 0.8))\n        painter.restore()\n",[27,834,835,845,861,877,887,900,917,921,930,940,944,954,964,981,986,1007,1026,1036,1041,1051,1056,1064,1090,1116,1132,1159,1184,1218],{"__ignoreMap":243},[247,836,837,839,842],{"class":114,"line":249},[247,838,353],{"class":252},[247,840,841],{"class":319}," startRender",[247,843,844],{"class":256},"(self, context):\n",[247,846,847,849,851,853,856,858],{"class":114,"line":260},[247,848,407],{"class":334},[247,850,443],{"class":256},[247,852,362],{"class":252},[247,854,855],{"class":256}," QPen(",[247,857,510],{"class":334},[247,859,860],{"class":256},".color())\n",[247,862,863,865,868,871,874],{"class":114,"line":274},[247,864,407],{"class":334},[247,866,867],{"class":256},"._pen.setCapStyle(",[247,869,870],{"class":334},"1",[247,872,873],{"class":256},")                 ",[247,875,876],{"class":421},"# Qt.FlatCap\n",[247,878,879,882,884],{"class":114,"line":280},[247,880,881],{"class":256},"        rc ",[247,883,362],{"class":252},[247,885,886],{"class":256}," context.renderContext()\n",[247,888,889,891,894,897],{"class":114,"line":293},[247,890,407],{"class":334},[247,892,893],{"class":256},"._pen.setWidthF(rc.convertToPainterUnits(",[247,895,896],{"class":334},"0.3",[247,898,899],{"class":256},", QgsUnitTypes.RenderMillimeters))\n",[247,901,902,904,907,909,912,914],{"class":114,"line":306},[247,903,407],{"class":334},[247,905,906],{"class":256},"._staff ",[247,908,362],{"class":252},[247,910,911],{"class":256}," rc.convertToPainterUnits(",[247,913,510],{"class":334},[247,915,916],{"class":256},".size(), QgsUnitTypes.RenderMillimeters)\n",[247,918,919],{"class":114,"line":313},[247,920,310],{"emptyLinePlaceholder":309},[247,922,923,925,928],{"class":114,"line":331},[247,924,353],{"class":252},[247,926,927],{"class":319}," stopRender",[247,929,844],{"class":256},[247,931,932,934,936,938],{"class":114,"line":345},[247,933,407],{"class":334},[247,935,443],{"class":256},[247,937,362],{"class":252},[247,939,448],{"class":334},[247,941,942],{"class":114,"line":350},[247,943,310],{"emptyLinePlaceholder":309},[247,945,946,948,951],{"class":114,"line":389},[247,947,353],{"class":252},[247,949,950],{"class":319}," renderPoint",[247,952,953],{"class":256},"(self, point, context):\n",[247,955,956,959,961],{"class":114,"line":404},[247,957,958],{"class":256},"        feature ",[247,960,362],{"class":252},[247,962,963],{"class":256}," context.feature()\n",[247,965,966,969,972,975,978],{"class":114,"line":413},[247,967,968],{"class":252},"        if",[247,970,971],{"class":256}," feature ",[247,973,974],{"class":252},"is",[247,976,977],{"class":334}," None",[247,979,980],{"class":256},":\n",[247,982,983],{"class":114,"line":425},[247,984,985],{"class":252},"            return\n",[247,987,988,991,993,996,998,1001,1004],{"class":114,"line":438},[247,989,990],{"class":256},"        speed ",[247,992,362],{"class":252},[247,994,995],{"class":256}," feature[",[247,997,510],{"class":334},[247,999,1000],{"class":256},".speed_field] ",[247,1002,1003],{"class":252},"or",[247,1005,1006],{"class":334}," 0\n",[247,1008,1009,1012,1014,1016,1019,1022,1024],{"class":114,"line":451},[247,1010,1011],{"class":256},"        direction ",[247,1013,362],{"class":252},[247,1015,995],{"class":256},[247,1017,1018],{"class":341},"\"dir_deg\"",[247,1020,1021],{"class":256},"] ",[247,1023,1003],{"class":252},[247,1025,1006],{"class":334},[247,1027,1028,1031,1033],{"class":114,"line":456},[247,1029,1030],{"class":256},"        painter ",[247,1032,362],{"class":252},[247,1034,1035],{"class":256}," context.renderContext().painter()\n",[247,1037,1038],{"class":114,"line":467},[247,1039,1040],{"class":256},"        painter.save()\n",[247,1042,1043,1046,1048],{"class":114,"line":481},[247,1044,1045],{"class":256},"        painter.setPen(",[247,1047,510],{"class":334},[247,1049,1050],{"class":256},"._pen)\n",[247,1052,1053],{"class":114,"line":486},[247,1054,1055],{"class":256},"        painter.translate(point)\n",[247,1057,1058,1061],{"class":114,"line":496},[247,1059,1060],{"class":256},"        painter.rotate(direction)                ",[247,1062,1063],{"class":421},"# 0 = wind from north\n",[247,1065,1066,1069,1071,1073,1075,1078,1080,1082,1085,1087],{"class":114,"line":516},[247,1067,1068],{"class":256},"        painter.drawLine(QPointF(",[247,1070,55],{"class":334},[247,1072,215],{"class":256},[247,1074,55],{"class":334},[247,1076,1077],{"class":256},"), QPointF(",[247,1079,55],{"class":334},[247,1081,215],{"class":256},[247,1083,1084],{"class":252},"-",[247,1086,510],{"class":334},[247,1088,1089],{"class":256},"._staff))\n",[247,1091,1092,1095,1097,1100,1102,1105,1108,1111,1114],{"class":114,"line":535},[247,1093,1094],{"class":256},"        feathers ",[247,1096,362],{"class":252},[247,1098,1099],{"class":334}," int",[247,1101,323],{"class":256},[247,1103,1104],{"class":334},"round",[247,1106,1107],{"class":256},"(speed ",[247,1109,1110],{"class":252},"\u002F",[247,1112,1113],{"class":334}," 10",[247,1115,627],{"class":256},[247,1117,1118,1121,1123,1125,1127,1129],{"class":114,"line":548},[247,1119,1120],{"class":256},"        step ",[247,1122,362],{"class":252},[247,1124,473],{"class":334},[247,1126,906],{"class":256},[247,1128,1110],{"class":252},[247,1130,1131],{"class":334}," 6\n",[247,1133,1134,1137,1140,1143,1146,1148,1151,1154,1156],{"class":114,"line":553},[247,1135,1136],{"class":252},"        for",[247,1138,1139],{"class":256}," i ",[247,1141,1142],{"class":252},"in",[247,1144,1145],{"class":334}," range",[247,1147,323],{"class":256},[247,1149,1150],{"class":334},"min",[247,1152,1153],{"class":256},"(feathers, ",[247,1155,68],{"class":334},[247,1157,1158],{"class":256},")):\n",[247,1160,1161,1164,1166,1169,1171,1173,1176,1178,1181],{"class":114,"line":562},[247,1162,1163],{"class":256},"            y ",[247,1165,362],{"class":252},[247,1167,1168],{"class":252}," -",[247,1170,510],{"class":334},[247,1172,906],{"class":256},[247,1174,1175],{"class":252},"+",[247,1177,1139],{"class":256},[247,1179,1180],{"class":252},"*",[247,1182,1183],{"class":256}," step\n",[247,1185,1186,1189,1191,1194,1196,1198,1200,1203,1206,1208,1211,1213,1216],{"class":114,"line":573},[247,1187,1188],{"class":256},"            painter.drawLine(QPointF(",[247,1190,55],{"class":334},[247,1192,1193],{"class":256},", y), QPointF(",[247,1195,510],{"class":334},[247,1197,906],{"class":256},[247,1199,1180],{"class":252},[247,1201,1202],{"class":334}," 0.35",[247,1204,1205],{"class":256},", y ",[247,1207,1084],{"class":252},[247,1209,1210],{"class":256}," step ",[247,1212,1180],{"class":252},[247,1214,1215],{"class":334}," 0.8",[247,1217,627],{"class":256},[247,1219,1220],{"class":114,"line":581},[247,1221,1222],{"class":256},"        painter.restore()\n",[14,1224,1225,1227,1228,1231,1232,222,1235,1238,1239,1241,1242,1244],{},[696,1226,698],{}," Converting millimetres to painter units through the render context is essential: on screen a millimetre is a few pixels, in a 300 dpi export it is about twelve, and hard-coded pixel sizes produce symbols that shrink in print. ",[27,1229,1230],{},"context.feature()"," gives the feature being drawn, so attributes can drive the drawing. ",[27,1233,1234],{},"painter.save()",[27,1236,1237],{},"restore()"," around the translation and rotation keep the painter clean for the next layer. In ",[27,1240,745],{},", also resolve field indexes once if you read attributes by index; looking names up in every ",[27,1243,235],{}," call costs time on large layers.",[183,1246,1248],{"id":1247},"register-the-type-from-a-plugin","Register the type from a plugin",[14,1250,1251],{},"The registry needs a metadata object that names the type and knows how to create instances from properties. Register it when the plugin loads and remove it when it unloads.",[238,1253,1255],{"className":240,"code":1254,"language":242,"meta":243,"style":243},"from qgis.core import QgsApplication, QgsSymbolLayerAbstractMetadata, Qgis\n\nclass WindBarbMetadata(QgsSymbolLayerAbstractMetadata):\n    def __init__(self):\n        super().__init__(WindBarbSymbolLayer.TYPE, \"Wind barb (Python)\",\n                         Qgis.SymbolType.Marker)\n\n    def createSymbolLayer(self, props):\n        return WindBarbSymbolLayer.create(props)\n\nclass WindBarbPlugin:\n    def __init__(self, iface):\n        self.iface = iface\n        self.metadata = None\n\n    def initGui(self):\n        self.metadata = WindBarbMetadata()\n        QgsApplication.symbolLayerRegistry().addSymbolLayerType(self.metadata)\n\n    def unload(self):\n        registry = QgsApplication.symbolLayerRegistry()\n        if hasattr(registry, \"removeSymbolLayerType\"):\n            registry.removeSymbolLayerType(self.metadata)\n",[27,1256,1257,1268,1272,1286,1294,1316,1321,1325,1335,1342,1346,1355,1364,1376,1387,1391,1400,1411,1421,1425,1434,1444,1459],{"__ignoreMap":243},[247,1258,1259,1261,1263,1265],{"class":114,"line":249},[247,1260,263],{"class":252},[247,1262,266],{"class":256},[247,1264,253],{"class":252},[247,1266,1267],{"class":256}," QgsApplication, QgsSymbolLayerAbstractMetadata, Qgis\n",[247,1269,1270],{"class":114,"line":260},[247,1271,310],{"emptyLinePlaceholder":309},[247,1273,1274,1276,1279,1281,1284],{"class":114,"line":274},[247,1275,316],{"class":252},[247,1277,1278],{"class":319}," WindBarbMetadata",[247,1280,323],{"class":256},[247,1282,1283],{"class":319},"QgsSymbolLayerAbstractMetadata",[247,1285,328],{"class":256},[247,1287,1288,1290,1292],{"class":114,"line":280},[247,1289,353],{"class":252},[247,1291,356],{"class":334},[247,1293,464],{"class":256},[247,1295,1296,1298,1300,1302,1305,1308,1310,1313],{"class":114,"line":293},[247,1297,392],{"class":334},[247,1299,395],{"class":256},[247,1301,398],{"class":334},[247,1303,1304],{"class":256},"(WindBarbSymbolLayer.",[247,1306,1307],{"class":334},"TYPE",[247,1309,215],{"class":256},[247,1311,1312],{"class":341},"\"Wind barb (Python)\"",[247,1314,1315],{"class":256},",\n",[247,1317,1318],{"class":114,"line":306},[247,1319,1320],{"class":256},"                         Qgis.SymbolType.Marker)\n",[247,1322,1323],{"class":114,"line":313},[247,1324,310],{"emptyLinePlaceholder":309},[247,1326,1327,1329,1332],{"class":114,"line":331},[247,1328,353],{"class":252},[247,1330,1331],{"class":319}," createSymbolLayer",[247,1333,1334],{"class":256},"(self, props):\n",[247,1336,1337,1339],{"class":114,"line":345},[247,1338,470],{"class":252},[247,1340,1341],{"class":256}," WindBarbSymbolLayer.create(props)\n",[247,1343,1344],{"class":114,"line":350},[247,1345,310],{"emptyLinePlaceholder":309},[247,1347,1348,1350,1353],{"class":114,"line":389},[247,1349,316],{"class":252},[247,1351,1352],{"class":319}," WindBarbPlugin",[247,1354,980],{"class":256},[247,1356,1357,1359,1361],{"class":114,"line":404},[247,1358,353],{"class":252},[247,1360,356],{"class":334},[247,1362,1363],{"class":256},"(self, iface):\n",[247,1365,1366,1368,1371,1373],{"class":114,"line":413},[247,1367,407],{"class":334},[247,1369,1370],{"class":256},".iface ",[247,1372,362],{"class":252},[247,1374,1375],{"class":256}," iface\n",[247,1377,1378,1380,1383,1385],{"class":114,"line":425},[247,1379,407],{"class":334},[247,1381,1382],{"class":256},".metadata ",[247,1384,362],{"class":252},[247,1386,448],{"class":334},[247,1388,1389],{"class":114,"line":438},[247,1390,310],{"emptyLinePlaceholder":309},[247,1392,1393,1395,1398],{"class":114,"line":451},[247,1394,353],{"class":252},[247,1396,1397],{"class":319}," initGui",[247,1399,464],{"class":256},[247,1401,1402,1404,1406,1408],{"class":114,"line":456},[247,1403,407],{"class":334},[247,1405,1382],{"class":256},[247,1407,362],{"class":252},[247,1409,1410],{"class":256}," WindBarbMetadata()\n",[247,1412,1413,1416,1418],{"class":114,"line":467},[247,1414,1415],{"class":256},"        QgsApplication.symbolLayerRegistry().addSymbolLayerType(",[247,1417,510],{"class":334},[247,1419,1420],{"class":256},".metadata)\n",[247,1422,1423],{"class":114,"line":481},[247,1424,310],{"emptyLinePlaceholder":309},[247,1426,1427,1429,1432],{"class":114,"line":486},[247,1428,353],{"class":252},[247,1430,1431],{"class":319}," unload",[247,1433,464],{"class":256},[247,1435,1436,1439,1441],{"class":114,"line":496},[247,1437,1438],{"class":256},"        registry ",[247,1440,362],{"class":252},[247,1442,1443],{"class":256}," QgsApplication.symbolLayerRegistry()\n",[247,1445,1446,1448,1451,1454,1457],{"class":114,"line":516},[247,1447,968],{"class":252},[247,1449,1450],{"class":334}," hasattr",[247,1452,1453],{"class":256},"(registry, ",[247,1455,1456],{"class":341},"\"removeSymbolLayerType\"",[247,1458,328],{"class":256},[247,1460,1461,1464,1466],{"class":114,"line":535},[247,1462,1463],{"class":256},"            registry.removeSymbolLayerType(",[247,1465,510],{"class":334},[247,1467,1420],{"class":256},[14,1469,1470,1472,1473,1476,1477,1480,1481,205],{},[696,1471,698],{}," After registration, \"Wind barb (Python)\" appears in the symbol layer type list of the symbol selector, and projects that use it load correctly — provided the plugin is enabled before the project opens. Keeping a reference to the metadata object on the plugin instance prevents Python from garbage-collecting it while QGIS still uses it. ",[27,1474,1475],{},"removeSymbolLayerType"," is only available on recent releases, hence the ",[27,1478,1479],{},"hasattr"," guard; where it is missing there is no clean removal, which is one reason symbol layer plugins usually ask for a restart after an update. On QGIS 3.x before 3.30, the symbol type enum is ",[27,1482,1483],{},"QgsSymbol.Marker",[183,1485,1487],{"id":1486},"use-it-from-python-and-in-styles","Use it from Python and in styles",[14,1489,1490],{},"Once registered, the layer is used like any built-in one: added to symbols, saved in QML, set as part of a renderer.",[238,1492,1494],{"className":240,"code":1493,"language":242,"meta":243,"style":243},"from qgis.core import QgsProject, QgsMarkerSymbol, QgsSingleSymbolRenderer\n\nstations = QgsProject.instance().mapLayersByName(\"weather_stations\")[0]\nsymbol = QgsMarkerSymbol()\nsymbol.changeSymbolLayer(0, WindBarbSymbolLayer(size=9))\nstations.setRenderer(QgsSingleSymbolRenderer(symbol))\nstations.triggerRepaint()\n\nstations.saveNamedStyle(\"\u002Fdata\u002Fstyles\u002Fwind_barbs.qml\")\n",[27,1495,1496,1507,1511,1532,1542,1563,1568,1573,1577],{"__ignoreMap":243},[247,1497,1498,1500,1502,1504],{"class":114,"line":249},[247,1499,263],{"class":252},[247,1501,266],{"class":256},[247,1503,253],{"class":252},[247,1505,1506],{"class":256}," QgsProject, QgsMarkerSymbol, QgsSingleSymbolRenderer\n",[247,1508,1509],{"class":114,"line":260},[247,1510,310],{"emptyLinePlaceholder":309},[247,1512,1513,1516,1518,1521,1524,1527,1529],{"class":114,"line":274},[247,1514,1515],{"class":256},"stations ",[247,1517,362],{"class":252},[247,1519,1520],{"class":256}," QgsProject.instance().mapLayersByName(",[247,1522,1523],{"class":341},"\"weather_stations\"",[247,1525,1526],{"class":256},")[",[247,1528,55],{"class":334},[247,1530,1531],{"class":256},"]\n",[247,1533,1534,1537,1539],{"class":114,"line":280},[247,1535,1536],{"class":256},"symbol ",[247,1538,362],{"class":252},[247,1540,1541],{"class":256}," QgsMarkerSymbol()\n",[247,1543,1544,1547,1549,1552,1556,1558,1561],{"class":114,"line":293},[247,1545,1546],{"class":256},"symbol.changeSymbolLayer(",[247,1548,55],{"class":334},[247,1550,1551],{"class":256},", WindBarbSymbolLayer(",[247,1553,1555],{"class":1554},"s9osk","size",[247,1557,362],{"class":252},[247,1559,1560],{"class":334},"9",[247,1562,627],{"class":256},[247,1564,1565],{"class":114,"line":306},[247,1566,1567],{"class":256},"stations.setRenderer(QgsSingleSymbolRenderer(symbol))\n",[247,1569,1570],{"class":114,"line":313},[247,1571,1572],{"class":256},"stations.triggerRepaint()\n",[247,1574,1575],{"class":114,"line":331},[247,1576,310],{"emptyLinePlaceholder":309},[247,1578,1579,1582,1585],{"class":114,"line":345},[247,1580,1581],{"class":256},"stations.saveNamedStyle(",[247,1583,1584],{"class":341},"\"\u002Fdata\u002Fstyles\u002Fwind_barbs.qml\"",[247,1586,1587],{"class":256},")\n",[14,1589,1590,1592,1593,1596,1597,1600],{},[696,1591,698],{}," Replacing the default simple marker with the custom layer gives a symbol made of the barb alone; ",[27,1594,1595],{},"appendSymbolLayer"," would draw it on top of a dot instead. The saved QML contains the layer type and its properties; any QGIS with the plugin enabled can load it, while QGIS without the plugin reports an unknown symbol layer type and substitutes a default marker. Built-in data-defined properties such as size and angle work automatically if your drawing code reads ",[27,1598,1599],{},"self.size()"," and lets the framework apply rotation — the example rotates itself from an attribute, which is simpler to follow but bypasses the standard angle property.",[183,1602,1604],{"id":1603},"add-a-settings-widget","Add a settings widget",[14,1606,1607,1608,1611,1612,1615],{},"Without a widget, users can add the layer but not change its properties in the symbol selector. A small ",[27,1609,1610],{},"QgsSymbolLayerWidget"," subclass returned from the metadata's ",[27,1613,1614],{},"createSymbolLayerWidget"," fills that gap.",[14,1617,1618],{},[38,1619,1622,1625,1628,1631,1638,1641,1645,1650,1654,1657,1663,1667,1672,1677,1681,1684,1688,1692,1695,1697,1701],{"viewBox":1620,"role":41,"ariaLabel":1621,"xmlns":43},"0 0 760 218","The metadata creates the symbol layer and its optional settings widget, which edits the layer and emits changed to refresh previews",[45,1623,1624],{},"Metadata, layer and widget",[49,1626,1627],{},"The metadata object creates both the symbol layer from properties and, optionally, a settings widget for the symbol selector. The widget receives the layer through setSymbolLayer, edits it, and emits changed so the preview and map update. Without a widget the layer works but shows no settings.",[53,1629],{"x":55,"y":55,"width":56,"height":1630,"fill":58},"218",[60,1632,1633],{},[63,1634,1636],{"id":1635,"viewBox":66,"refX":67,"refY":68,"markerWidth":69,"markerHeight":69,"orient":70},"slWidgetArrow",[72,1637],{"d":74,"fill":75},[77,1639,1640],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":83},"Optional, but users will expect it",[53,1642],{"x":87,"y":1643,"width":798,"height":1644,"rx":67,"fill":91,"stroke":92,"style":93},"60","120",[77,1646,1649],{"x":1647,"y":1648,"style":135,"fill":82,"textAnchor":83},"134","99.6","metadata",[77,1651,1653],{"x":1647,"y":1652,"style":140,"fill":92,"textAnchor":83},"123.6","createSymbolLayer",[77,1655,1614],{"x":1647,"y":1656,"style":140,"fill":92,"textAnchor":83},"147.6",[114,1658],{"x1":1659,"y1":1660,"x2":1661,"y2":149,"stroke":75,"style":1662},"244","100","286","stroke-width:1.8;marker-end:url(#slWidgetArrow)",[114,1664],{"x1":1659,"y1":1665,"x2":1661,"y2":1666,"stroke":75,"style":1662},"140","160",[53,1668],{"x":1669,"y":1670,"width":798,"height":1671,"rx":67,"fill":129,"stroke":130,"style":93},"290","44","72",[77,1673,1676],{"x":1674,"y":1675,"style":98,"fill":130,"textAnchor":83},"400","72.78","symbol layer",[77,1678,1680],{"x":1674,"y":1679,"style":103,"fill":92,"textAnchor":83},"94.78","drawn on the map",[53,1682],{"x":1669,"y":1683,"width":798,"height":1671,"rx":67,"fill":150,"stroke":151,"style":93},"130",[77,1685,1687],{"x":1674,"y":1686,"style":98,"fill":151,"textAnchor":83},"158.78","settings widget",[77,1689,1691],{"x":1674,"y":1690,"style":103,"fill":92,"textAnchor":83},"180.78","edits the layer",[114,1693],{"x1":162,"y1":1694,"x2":164,"y2":1694,"stroke":75,"style":1662},"166",[53,1696],{"x":167,"y":1683,"width":168,"height":1671,"rx":67,"fill":169,"stroke":170,"style":93},[77,1698,1700],{"x":173,"y":1686,"style":98,"fill":1699,"textAnchor":83},"#166534","changed signal",[77,1702,1703],{"x":173,"y":1690,"style":103,"fill":92,"textAnchor":83},"preview refresh",[238,1705,1707],{"className":240,"code":1706,"language":242,"meta":243,"style":243},"from qgis.gui import QgsSymbolLayerWidget\nfrom qgis.PyQt.QtWidgets import QDoubleSpinBox, QLineEdit, QFormLayout\n\nclass WindBarbWidget(QgsSymbolLayerWidget):\n    def __init__(self, vector_layer=None):\n        super().__init__(None, vector_layer)\n        self.layer = None\n        self.size = QDoubleSpinBox(); self.size.setRange(1, 50); self.size.setSuffix(\" mm\")\n        self.field = QLineEdit()\n        form = QFormLayout(self); form.addRow(\"Staff length\", self.size); form.addRow(\"Speed field\", self.field)\n        self.size.valueChanged.connect(self._update)\n        self.field.editingFinished.connect(self._update)\n\n    def setSymbolLayer(self, layer):\n        self.layer = layer\n        self.size.setValue(layer.size()); self.field.setText(layer.speed_field)\n\n    def symbolLayer(self):\n        return self.layer\n\n    def _update(self):\n        self.layer.setSize(self.size.value()); self.layer.speed_field = self.field.text()\n        self.changed.emit()\n\n# in WindBarbMetadata:\n#     def createSymbolLayerWidget(self, vector_layer):\n#         return WindBarbWidget(vector_layer)\n",[27,1708,1709,1721,1733,1737,1750,1766,1781,1792,1829,1841,1876,1888,1899,1903,1913,1924,1936,1940,1949,1958,1962,1971,1995,2002,2006,2011,2016],{"__ignoreMap":243},[247,1710,1711,1713,1716,1718],{"class":114,"line":249},[247,1712,263],{"class":252},[247,1714,1715],{"class":256}," qgis.gui ",[247,1717,253],{"class":252},[247,1719,1720],{"class":256}," QgsSymbolLayerWidget\n",[247,1722,1723,1725,1728,1730],{"class":114,"line":260},[247,1724,263],{"class":252},[247,1726,1727],{"class":256}," qgis.PyQt.QtWidgets ",[247,1729,253],{"class":252},[247,1731,1732],{"class":256}," QDoubleSpinBox, QLineEdit, QFormLayout\n",[247,1734,1735],{"class":114,"line":274},[247,1736,310],{"emptyLinePlaceholder":309},[247,1738,1739,1741,1744,1746,1748],{"class":114,"line":280},[247,1740,316],{"class":252},[247,1742,1743],{"class":319}," WindBarbWidget",[247,1745,323],{"class":256},[247,1747,1610],{"class":319},[247,1749,328],{"class":256},[247,1751,1752,1754,1756,1759,1761,1764],{"class":114,"line":293},[247,1753,353],{"class":252},[247,1755,356],{"class":334},[247,1757,1758],{"class":256},"(self, vector_layer",[247,1760,362],{"class":252},[247,1762,1763],{"class":334},"None",[247,1765,328],{"class":256},[247,1767,1768,1770,1772,1774,1776,1778],{"class":114,"line":306},[247,1769,392],{"class":334},[247,1771,395],{"class":256},[247,1773,398],{"class":334},[247,1775,323],{"class":256},[247,1777,1763],{"class":334},[247,1779,1780],{"class":256},", vector_layer)\n",[247,1782,1783,1785,1788,1790],{"class":114,"line":313},[247,1784,407],{"class":334},[247,1786,1787],{"class":256},".layer ",[247,1789,362],{"class":252},[247,1791,448],{"class":334},[247,1793,1794,1796,1799,1801,1804,1806,1809,1811,1813,1816,1819,1821,1824,1827],{"class":114,"line":331},[247,1795,407],{"class":334},[247,1797,1798],{"class":256},".size ",[247,1800,362],{"class":252},[247,1802,1803],{"class":256}," QDoubleSpinBox(); ",[247,1805,510],{"class":334},[247,1807,1808],{"class":256},".size.setRange(",[247,1810,870],{"class":334},[247,1812,215],{"class":256},[247,1814,1815],{"class":334},"50",[247,1817,1818],{"class":256},"); ",[247,1820,510],{"class":334},[247,1822,1823],{"class":256},".size.setSuffix(",[247,1825,1826],{"class":341},"\" mm\"",[247,1828,1587],{"class":256},[247,1830,1831,1833,1836,1838],{"class":114,"line":345},[247,1832,407],{"class":334},[247,1834,1835],{"class":256},".field ",[247,1837,362],{"class":252},[247,1839,1840],{"class":256}," QLineEdit()\n",[247,1842,1843,1846,1848,1851,1853,1856,1859,1861,1863,1866,1869,1871,1873],{"class":114,"line":350},[247,1844,1845],{"class":256},"        form ",[247,1847,362],{"class":252},[247,1849,1850],{"class":256}," QFormLayout(",[247,1852,510],{"class":334},[247,1854,1855],{"class":256},"); form.addRow(",[247,1857,1858],{"class":341},"\"Staff length\"",[247,1860,215],{"class":256},[247,1862,510],{"class":334},[247,1864,1865],{"class":256},".size); form.addRow(",[247,1867,1868],{"class":341},"\"Speed field\"",[247,1870,215],{"class":256},[247,1872,510],{"class":334},[247,1874,1875],{"class":256},".field)\n",[247,1877,1878,1880,1883,1885],{"class":114,"line":389},[247,1879,407],{"class":334},[247,1881,1882],{"class":256},".size.valueChanged.connect(",[247,1884,510],{"class":334},[247,1886,1887],{"class":256},"._update)\n",[247,1889,1890,1892,1895,1897],{"class":114,"line":404},[247,1891,407],{"class":334},[247,1893,1894],{"class":256},".field.editingFinished.connect(",[247,1896,510],{"class":334},[247,1898,1887],{"class":256},[247,1900,1901],{"class":114,"line":413},[247,1902,310],{"emptyLinePlaceholder":309},[247,1904,1905,1907,1910],{"class":114,"line":425},[247,1906,353],{"class":252},[247,1908,1909],{"class":319}," setSymbolLayer",[247,1911,1912],{"class":256},"(self, layer):\n",[247,1914,1915,1917,1919,1921],{"class":114,"line":438},[247,1916,407],{"class":334},[247,1918,1787],{"class":256},[247,1920,362],{"class":252},[247,1922,1923],{"class":256}," layer\n",[247,1925,1926,1928,1931,1933],{"class":114,"line":451},[247,1927,407],{"class":334},[247,1929,1930],{"class":256},".size.setValue(layer.size()); ",[247,1932,510],{"class":334},[247,1934,1935],{"class":256},".field.setText(layer.speed_field)\n",[247,1937,1938],{"class":114,"line":456},[247,1939,310],{"emptyLinePlaceholder":309},[247,1941,1942,1944,1947],{"class":114,"line":467},[247,1943,353],{"class":252},[247,1945,1946],{"class":319}," symbolLayer",[247,1948,464],{"class":256},[247,1950,1951,1953,1955],{"class":114,"line":481},[247,1952,470],{"class":252},[247,1954,473],{"class":334},[247,1956,1957],{"class":256},".layer\n",[247,1959,1960],{"class":114,"line":486},[247,1961,310],{"emptyLinePlaceholder":309},[247,1963,1964,1966,1969],{"class":114,"line":496},[247,1965,353],{"class":252},[247,1967,1968],{"class":319}," _update",[247,1970,464],{"class":256},[247,1972,1973,1975,1978,1980,1983,1985,1988,1990,1992],{"class":114,"line":516},[247,1974,407],{"class":334},[247,1976,1977],{"class":256},".layer.setSize(",[247,1979,510],{"class":334},[247,1981,1982],{"class":256},".size.value()); ",[247,1984,510],{"class":334},[247,1986,1987],{"class":256},".layer.speed_field ",[247,1989,362],{"class":252},[247,1991,473],{"class":334},[247,1993,1994],{"class":256},".field.text()\n",[247,1996,1997,1999],{"class":114,"line":535},[247,1998,407],{"class":334},[247,2000,2001],{"class":256},".changed.emit()\n",[247,2003,2004],{"class":114,"line":548},[247,2005,310],{"emptyLinePlaceholder":309},[247,2007,2008],{"class":114,"line":553},[247,2009,2010],{"class":421},"# in WindBarbMetadata:\n",[247,2012,2013],{"class":114,"line":562},[247,2014,2015],{"class":421},"#     def createSymbolLayerWidget(self, vector_layer):\n",[247,2017,2018],{"class":114,"line":573},[247,2019,2020],{"class":421},"#         return WindBarbWidget(vector_layer)\n",[14,2022,2023,2025,2026,2029,2030,2033,2034,2038],{},[696,2024,698],{}," The widget holds a reference to the layer being edited, updates it directly, and emits ",[27,2027,2028],{},"changed"," so the symbol selector redraws its preview. A ",[27,2031,2032],{},"QgsFieldExpressionWidget"," in place of the line edit would let users pick the speed field from the layer — the ",[21,2035,2037],{"href":2036},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer\u002F","QGIS custom widgets"," recipe shows it.",[183,2040,2042],{"id":2041},"performance-and-limits","Performance and limits",[14,2044,2045,2046,2048,2049,2051],{},"Python symbol layers run Python code for every feature in every render, including every pan and zoom. A few thousand features render comfortably; hundreds of thousands will feel slow. Keep ",[27,2047,235],{}," minimal — no allocations, no attribute lookups by name, no geometry operations — and precompute in ",[27,2050,745],{},". Rendering happens in worker threads, so the layer must not touch the GUI or shared mutable state. For many needs, a geometry generator or data-defined built-in layer is fast enough and requires no plugin; reach for a custom class when the drawing logic genuinely cannot be expressed that way.",[183,2053,2055],{"id":2054},"qgis-version-compatibility","QGIS version compatibility",[14,2057,2058,2060,2061,2063,2064,2067,2068,2070,2071,2074,2075,2078,2079,2082,2083,205],{},[27,2059,29],{}," subclassing and ",[27,2062,1283],{}," work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. ",[27,2065,2066],{},"Qgis.SymbolType.Marker"," exists from 3.20; earlier code uses ",[27,2069,1483],{},". Removing a registered type needs a recent release, as noted above. On QGIS 4, ",[27,2072,2073],{},"QgsUnitTypes.RenderMillimeters"," is ",[27,2076,2077],{},"Qgis.RenderUnit.Millimeters",", and ",[27,2080,2081],{},"QPen.setCapStyle"," takes ",[27,2084,2085],{},"Qt.PenCapStyle.FlatCap",[183,2087,2089],{"id":2088},"troubleshooting","Troubleshooting",[188,2091,2092,2098,2104,2110],{},[191,2093,2094,2097],{},[696,2095,2096],{},"The type does not appear in the selector."," Registration ran after the selector was opened, or the metadata was garbage-collected; keep a reference.",[191,2099,2100,2103],{},[696,2101,2102],{},"Symbols are tiny in exports."," Sizes were in pixels; convert from millimetres through the render context.",[191,2105,2106,2109],{},[696,2107,2108],{},"Projects show a default marker instead."," The plugin was not enabled before the project loaded.",[191,2111,2112,2115],{},[696,2113,2114],{},"QGIS crashes while panning."," The layer touches GUI objects or shared state from the render thread.",[183,2117,2119],{"id":2118},"conclusion","Conclusion",[14,2121,2122,2123,2125,2126,2128,2129,2131,2132,2134,2135,2137,2138,2140],{},"Subclass ",[27,2124,29],{}," with a unique type name, serialise everything in ",[27,2127,710],{}," and restore it in ",[27,2130,721],{},", copy data-defined properties in ",[27,2133,732],{},", prepare in ",[27,2136,745],{}," and draw in ",[27,2139,235],{}," with unit conversion, register through metadata from a plugin, add a settings widget, and keep the per-feature code lean.",[183,2142,2144],{"id":2143},"frequently-asked-questions","Frequently Asked Questions",[14,2146,2147,2150,2151,2154,2155,2158,2159,2154,2162,2165],{},[696,2148,2149],{},"Can I write line or fill symbol layers too?","\nYes: subclass ",[27,2152,2153],{},"QgsLineSymbolLayer"," (",[27,2156,2157],{},"renderPolyline",") or ",[27,2160,2161],{},"QgsFillSymbolLayer",[27,2163,2164],{},"renderPolygon",") in the same way.",[14,2167,2168,2171],{},[696,2169,2170],{},"Will the symbol work in QGIS Server?","\nOnly if the plugin is installed and loaded on the server, which server plugins support.",[14,2173,2174,2177,2178,205],{},[696,2175,2176],{},"Does SLD export include my layer?","\nNo. Custom layers have no SLD representation unless you implement ",[27,2179,2180],{},"toSld",[14,2182,2183,2186,2187,205],{},[696,2184,2185],{},"Can I use SVG files instead?","\nOften yes — an SVG marker with data-defined parameters may avoid custom code entirely; see ",[21,2188,2190],{"href":2189},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fuse-svg-marker-symbol-pyqgis\u002F","using SVG marker symbols",[183,2192,2194],{"id":2193},"related","Related",[188,2196,2197,2202,2208,2214,2220],{},[191,2198,2199,2201],{},[21,2200,24],{"href":23}," — the guide this recipe belongs to",[191,2203,2204],{},[21,2205,2207],{"href":2206},"\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-custom-feature-renderer-pyqgis\u002F","Write a Custom Feature Renderer in PyQGIS",[191,2209,2210],{},[21,2211,2213],{"href":2212},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fstack-symbol-layers-pyqgis\u002F","Stack Symbol Layers in PyQGIS",[191,2215,2216],{},[21,2217,2219],{"href":2218},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fgeometry-generator-symbol-layer-pyqgis\u002F","Geometry Generator Symbol Layer in PyQGIS",[191,2221,2222],{},[21,2223,2225],{"href":2224},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Freload-qgis-plugin-without-restart\u002F","Reload a QGIS Plugin Without Restarting",[2227,2228,2229],"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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":243,"searchDepth":260,"depth":260,"links":2231},[2232,2233,2234,2235,2236,2237,2238,2239,2240,2241,2242,2243],{"id":185,"depth":260,"text":186},{"id":228,"depth":260,"text":229},{"id":736,"depth":260,"text":737},{"id":1247,"depth":260,"text":1248},{"id":1486,"depth":260,"text":1487},{"id":1603,"depth":260,"text":1604},{"id":2041,"depth":260,"text":2042},{"id":2054,"depth":260,"text":2055},{"id":2088,"depth":260,"text":2089},{"id":2118,"depth":260,"text":2119},{"id":2143,"depth":260,"text":2144},{"id":2193,"depth":260,"text":2194},"Create your own marker symbol layer by subclassing QgsMarkerSymbolLayer — drawing with QPainter in render units, exposing properties so styles save and load, registering it with the symbol layer registry from a plugin, and keeping it fast enough for thousands of features.","md",{"slug":2247,"type":2248,"breadcrumb":2249,"datePublished":2250,"dateModified":2250},"write-custom-symbol-layer-pyqgis","article","Write a Custom Symbol Layer","2026-10-02","\u002Fqgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-custom-symbol-layer-pyqgis",{"title":5,"description":2244},"qgis-plugin-development\u002Fextending-qgis-with-custom-classes\u002Fwrite-custom-symbol-layer-pyqgis\u002Findex","YROLs0-gTfofCvgkI5ezbQPMka9PgwbaMo0K49IA5aY",1790966257307]