[{"data":1,"prerenderedAt":1432},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fapply-map-theme-to-layout-map-pyqgis":3},{"id":4,"title":5,"body":6,"description":1421,"extension":1422,"meta":1423,"navigation":243,"path":1428,"seo":1429,"stem":1430,"__hash__":1431},"docs\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fapply-map-theme-to-layout-map-pyqgis\u002Findex.md","Apply a Map Theme to a Layout Map in PyQGIS",{"type":7,"value":8,"toc":1407},"minimark",[9,13,17,26,170,175,202,206,209,325,343,353,403,419,423,430,476,493,609,613,616,759,768,772,775,843,852,855,902,916,920,923,926,954,959,963,966,1156,1174,1178,1184,1269,1273,1325,1329,1335,1339,1345,1354,1364,1370,1374,1403],[10,11,5],"h1",{"id":12},"apply-a-map-theme-to-a-layout-map-in-pyqgis",[14,15,16],"p",{},"A layout map item shows whatever the canvas shows, which is convenient until you need four maps that differ only in content. Point the item at a map theme instead and the template stops depending on what happens to be ticked: the item renders the theme, the legend follows it, and a loop over the theme names produces the whole set.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002F","Map Themes & Layer Visibility in PyQGIS",". It covers linking an item to a theme, the three ways a layout map decides what to draw, keeping a legend in step, driving several map items from different themes on one page, and exporting a series.",[14,27,28],{},[29,30,35,39,43,50,67,76,86,92,97,101,105,109,114,117,121,124,127,130,135,139,142,146,150,154,161,166],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 320","img","The three sources a layout map item can draw from: the current canvas, a fixed list of layers set on the item, or a named map theme","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three things a layout map item can follow",[40,41,42],"desc",{},"By default a layout map item mirrors the canvas, so its content changes whenever someone ticks a layer. Setting a fixed layer list pins it to specific layers but not to their styles. Following a map theme pins both the layers and the styles they use, and lets a legend follow the same theme.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","320","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"ltArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Only one of these survives somebody ticking a layer",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"20","56","228","150","10","#fdf2e2","#b91c1c","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"134","82","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","follows the canvas",[68,93,96],{"x":88,"y":94,"style":95,"fill":66,"textAnchor":74},"108","text-anchor:middle;font-size:10.5px;font-family:sans-serif","the default",[68,98,100],{"x":88,"y":99,"style":95,"fill":66,"textAnchor":74},"132","whatever is ticked now",[68,102,104],{"x":88,"y":103,"style":95,"fill":84,"textAnchor":74},"162","changes under you",[68,106,108],{"x":88,"y":107,"style":95,"fill":84,"textAnchor":74},"184","between exports",[44,110],{"x":111,"y":79,"width":80,"height":81,"rx":82,"fill":112,"stroke":113,"style":85},"266","#fffdf7","#b45309",[68,115,116],{"x":70,"y":89,"style":90,"fill":113,"textAnchor":74},"fixed layer list",[68,118,120],{"x":70,"y":94,"style":119,"fill":66,"textAnchor":74},"text-anchor:middle;font-size:10.5px;font-family:monospace","setLayers(…)",[68,122,123],{"x":70,"y":99,"style":95,"fill":66,"textAnchor":74},"layers pinned",[68,125,126],{"x":70,"y":103,"style":95,"fill":113,"textAnchor":74},"but styles still change",[68,128,129],{"x":70,"y":107,"style":95,"fill":113,"textAnchor":74},"if somebody restyles a layer",[44,131],{"x":132,"y":79,"width":80,"height":81,"rx":82,"fill":133,"stroke":134,"style":85},"512","#edf8e9","#15803d",[68,136,138],{"x":137,"y":89,"style":90,"fill":134,"textAnchor":74},"626","follows a theme",[68,140,141],{"x":137,"y":94,"style":119,"fill":66,"textAnchor":74},"setFollowVisibility",[68,143,145],{"x":137,"y":144,"style":119,"fill":66,"textAnchor":74},"126","Preset(True)",[68,147,149],{"x":137,"y":148,"style":95,"fill":134,"textAnchor":74},"152","layers AND styles pinned",[68,151,153],{"x":137,"y":152,"style":95,"fill":66,"textAnchor":74},"180","the legend can follow too",[44,155],{"x":152,"y":156,"width":157,"height":158,"rx":58,"fill":159,"stroke":160,"style":85},"238","400","60","#eef7f4","#0f766e",[68,162,165],{"x":70,"y":163,"style":164,"fill":160,"textAnchor":74},"262","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","one template · n themes · n deliverables",[68,167,169],{"x":70,"y":168,"style":95,"fill":66,"textAnchor":74},"284","the loop is four lines",[171,172,174],"h2",{"id":173},"prerequisites","Prerequisites",[176,177,178,186,194],"ul",{},[179,180,181,185],"li",{},[182,183,184],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[179,187,188,189,193],{},"A project containing at least two ",[21,190,192],{"href":191},"\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fcreate-map-theme-pyqgis\u002F","map themes",".",[179,195,196,197,201],{},"A layout with a map item whose ",[198,199,200],"em",{},"id"," has been set in its item properties; positional lookup works but breaks the first time somebody rearranges the page.",[171,203,205],{"id":204},"link-the-item-to-a-theme","Link the item to a theme",[14,207,208],{},"Two calls, and the order does not matter as long as both are made.",[210,211,216],"pre",{"className":212,"code":213,"language":214,"meta":215,"style":215},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject\n\nproject = QgsProject.instance()\nlayout = project.layoutManager().layoutByName(\"A3 landscape\")\nmap_item = layout.itemById(\"main map\")\n\nmap_item.setFollowVisibilityPreset(True)\nmap_item.setFollowVisibilityPresetName(\"flood risk\")\nlayout.refresh()\n","python","",[217,218,219,238,245,257,275,291,296,308,319],"code",{"__ignoreMap":215},[220,221,224,228,232,235],"span",{"class":222,"line":223},"line",1,[220,225,227],{"class":226},"snl16","from",[220,229,231],{"class":230},"s95oV"," qgis.core ",[220,233,234],{"class":226},"import",[220,236,237],{"class":230}," QgsProject\n",[220,239,241],{"class":222,"line":240},2,[220,242,244],{"emptyLinePlaceholder":243},true,"\n",[220,246,248,251,254],{"class":222,"line":247},3,[220,249,250],{"class":230},"project ",[220,252,253],{"class":226},"=",[220,255,256],{"class":230}," QgsProject.instance()\n",[220,258,260,263,265,268,272],{"class":222,"line":259},4,[220,261,262],{"class":230},"layout ",[220,264,253],{"class":226},[220,266,267],{"class":230}," project.layoutManager().layoutByName(",[220,269,271],{"class":270},"sU2Wk","\"A3 landscape\"",[220,273,274],{"class":230},")\n",[220,276,278,281,283,286,289],{"class":222,"line":277},5,[220,279,280],{"class":230},"map_item ",[220,282,253],{"class":226},[220,284,285],{"class":230}," layout.itemById(",[220,287,288],{"class":270},"\"main map\"",[220,290,274],{"class":230},[220,292,294],{"class":222,"line":293},6,[220,295,244],{"emptyLinePlaceholder":243},[220,297,299,302,306],{"class":222,"line":298},7,[220,300,301],{"class":230},"map_item.setFollowVisibilityPreset(",[220,303,305],{"class":304},"sDLfK","True",[220,307,274],{"class":230},[220,309,311,314,317],{"class":222,"line":310},8,[220,312,313],{"class":230},"map_item.setFollowVisibilityPresetName(",[220,315,316],{"class":270},"\"flood risk\"",[220,318,274],{"class":230},[220,320,322],{"class":222,"line":321},9,[220,323,324],{"class":230},"layout.refresh()\n",[14,326,327,330,331,334,335,338,339,342],{},[182,328,329],{},"Breakdown:"," The API says ",[198,332,333],{},"preset"," where the interface says ",[198,336,337],{},"theme","; they are the same thing, and the old name survives for compatibility. Setting only the name does nothing at all — the boolean is the switch, and forgetting it is the single most common failure here, made worse by the fact that the layout keeps rendering the canvas quite happily so nothing looks broken. ",[217,340,341],{},"layout.refresh()"," invalidates the item's cached render; without it an export can write the previous theme under the new name.",[14,344,345,348,349,352],{},[217,346,347],{},"itemById()"," returns ",[217,350,351],{},"None"," for an id that does not exist, so a script that will run unattended should say so plainly:",[210,354,356],{"className":212,"code":355,"language":214,"meta":215,"style":215},"map_item = layout.itemById(\"main map\")\nif map_item is None:\n    raise LookupError(\"layout has no item with id 'main map'\")\n",[217,357,358,370,387],{"__ignoreMap":215},[220,359,360,362,364,366,368],{"class":222,"line":223},[220,361,280],{"class":230},[220,363,253],{"class":226},[220,365,285],{"class":230},[220,367,288],{"class":270},[220,369,274],{"class":230},[220,371,372,375,378,381,384],{"class":222,"line":240},[220,373,374],{"class":226},"if",[220,376,377],{"class":230}," map_item ",[220,379,380],{"class":226},"is",[220,382,383],{"class":304}," None",[220,385,386],{"class":230},":\n",[220,388,389,392,395,398,401],{"class":222,"line":247},[220,390,391],{"class":226},"    raise",[220,393,394],{"class":304}," LookupError",[220,396,397],{"class":230},"(",[220,399,400],{"class":270},"\"layout has no item with id 'main map'\"",[220,402,274],{"class":230},[14,404,405,407,408,411,412,414,415,418],{},[182,406,329],{}," The alternative — iterating ",[217,409,410],{},"layout.items()"," and matching on type — finds ",[198,413,21],{}," map item rather than ",[198,416,417],{},"the"," map item, which is fine on a single-map page and wrong on a page with an inset. Setting ids in the layout is a two-second job that removes this whole class of ambiguity.",[171,420,422],{"id":421},"keep-the-legend-honest","Keep the legend honest",[14,424,425,426,429],{},"A legend that lists layers the map is not showing is worse than no legend. ",[217,427,428],{},"QgsLayoutItemLegend"," can follow the same theme.",[210,431,433],{"className":212,"code":432,"language":214,"meta":215,"style":215},"legend = layout.itemById(\"main legend\")\nlegend.setLinkedMap(map_item)\nlegend.setLegendFilterByMapEnabled(True)\nlegend.setAutoUpdateModel(True)\nlayout.refresh()\n",[217,434,435,449,454,463,472],{"__ignoreMap":215},[220,436,437,440,442,444,447],{"class":222,"line":223},[220,438,439],{"class":230},"legend ",[220,441,253],{"class":226},[220,443,285],{"class":230},[220,445,446],{"class":270},"\"main legend\"",[220,448,274],{"class":230},[220,450,451],{"class":222,"line":240},[220,452,453],{"class":230},"legend.setLinkedMap(map_item)\n",[220,455,456,459,461],{"class":222,"line":247},[220,457,458],{"class":230},"legend.setLegendFilterByMapEnabled(",[220,460,305],{"class":304},[220,462,274],{"class":230},[220,464,465,468,470],{"class":222,"line":259},[220,466,467],{"class":230},"legend.setAutoUpdateModel(",[220,469,305],{"class":304},[220,471,274],{"class":230},[220,473,474],{"class":222,"line":277},[220,475,324],{"class":230},[14,477,478,480,481,484,485,488,489,492],{},[182,479,329],{}," ",[217,482,483],{},"setLinkedMap()"," tells the legend which map item it describes, which is what allows the other two settings to mean anything. ",[217,486,487],{},"setLegendFilterByMapEnabled(True)"," removes entries for layers with no features inside the map item's extent — a genuine improvement on an atlas where most pages contain only some of the classes. ",[217,490,491],{},"setAutoUpdateModel(True)"," keeps the legend's tree in step with the map's layers; turn it off only when you have hand-edited legend entries you want preserved, because rebuilding the model discards those edits.",[14,494,495],{},[29,496,499,502,505,508,515,518,527,532,539,545,548,554,561,565,569,575,581,583,586,590,594,598,604],{"viewBox":497,"role":32,"ariaLabel":498,"xmlns":34},"0 0 760 288","A layout page with a map item following a theme and a legend linked to that map item, so the legend lists exactly the layers the map draws",[36,500,501],{},"A legend linked to a themed map",[40,503,504],{},"The page holds a map item that follows the flood risk theme and a legend linked to that item. Because the legend is linked and filtered by the map, it lists only the three layers the theme shows, and drops entries whose features fall outside the visible extent.",[44,506],{"x":46,"y":46,"width":47,"height":507,"fill":49},"288",[51,509,510],{},[54,511,513],{"id":512,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"lgArrow",[63,514],{"d":65,"fill":66},[68,516,517],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"The legend describes the map item, not the project",[44,519],{"x":520,"y":521,"width":522,"height":523,"rx":524,"fill":112,"stroke":525,"style":526},"90","50","580","216","6","#59645f","stroke-width:2",[68,528,531],{"x":70,"y":529,"style":530,"fill":525,"textAnchor":74},"72","text-anchor:middle;font-size:11px;font-family:sans-serif","A3 landscape",[44,533],{"x":534,"y":535,"width":536,"height":81,"rx":537,"fill":538,"stroke":160,"style":85},"116","86","330","4","#e7e2d4",[68,540,544],{"x":541,"y":542,"style":543,"fill":73,"textAnchor":74},"281","110","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","map item · id \"main map\"",[68,546,547],{"x":541,"y":99,"style":95,"fill":66,"textAnchor":74},"follows theme \"flood risk\"",[63,549],{"d":550,"fill":551,"stroke":552,"style":553},"M150 200 L206 170 L262 190 L330 156 L410 178","none","#2563eb","stroke-width:3",[44,555],{"x":81,"y":81,"width":556,"height":557,"rx":558,"fill":559,"stroke":552,"style":560},"80","34","3","#dbeafe","stroke-width:1.6",[44,562],{"x":563,"y":535,"width":564,"height":81,"rx":537,"fill":112,"stroke":134,"style":85},"472","172",[68,566,568],{"x":567,"y":542,"style":543,"fill":134,"textAnchor":74},"558","legend",[44,570],{"x":571,"y":572,"width":573,"height":573,"fill":559,"stroke":552,"style":574},"490","124","14","stroke-width:1.4",[68,576,580],{"x":577,"y":578,"style":579,"fill":66},"514","136","font-size:10px;font-family:sans-serif","flood extent",[44,582],{"x":571,"y":81,"width":573,"height":573,"fill":538,"stroke":73,"style":574},[68,584,585],{"x":577,"y":103,"style":579,"fill":66},"buildings",[44,587],{"x":571,"y":588,"width":573,"height":573,"fill":589,"stroke":134,"style":574},"176","#e8efe6",[68,591,593],{"x":577,"y":592,"style":579,"fill":66},"188","basemap",[68,595,597],{"x":567,"y":523,"style":596,"fill":525,"textAnchor":74},"text-anchor:middle;font-size:10px;font-family:sans-serif","land use is absent",[222,599],{"x1":600,"y1":601,"x2":602,"y2":601,"stroke":66,"style":603},"452","160","466","stroke-width:2;marker-end:url(#lgArrow)",[68,605,608],{"x":606,"y":607,"style":119,"fill":66,"textAnchor":74},"459","252","setLinkedMap(map_item)",[171,610,612],{"id":611},"several-maps-several-themes-one-page","Several maps, several themes, one page",[14,614,615],{},"A comparison page is the case that makes themes indispensable: the same extent, drawn four ways.",[210,617,619],{"className":212,"code":618,"language":214,"meta":215,"style":215},"PANELS = {\n    \"panel nw\": \"base\",\n    \"panel ne\": \"flood risk\",\n    \"panel sw\": \"land use\",\n    \"panel se\": \"night\",\n}\n\nreference = layout.itemById(\"panel nw\")\nfor item_id, theme_name in PANELS.items():\n    item = layout.itemById(item_id)\n    item.setFollowVisibilityPreset(True)\n    item.setFollowVisibilityPresetName(theme_name)\n    item.zoomToExtent(reference.extent())\nlayout.refresh()\n",[217,620,621,632,646,657,669,681,686,690,704,721,732,742,748,754],{"__ignoreMap":215},[220,622,623,626,629],{"class":222,"line":223},[220,624,625],{"class":304},"PANELS",[220,627,628],{"class":226}," =",[220,630,631],{"class":230}," {\n",[220,633,634,637,640,643],{"class":222,"line":240},[220,635,636],{"class":270},"    \"panel nw\"",[220,638,639],{"class":230},": ",[220,641,642],{"class":270},"\"base\"",[220,644,645],{"class":230},",\n",[220,647,648,651,653,655],{"class":222,"line":247},[220,649,650],{"class":270},"    \"panel ne\"",[220,652,639],{"class":230},[220,654,316],{"class":270},[220,656,645],{"class":230},[220,658,659,662,664,667],{"class":222,"line":259},[220,660,661],{"class":270},"    \"panel sw\"",[220,663,639],{"class":230},[220,665,666],{"class":270},"\"land use\"",[220,668,645],{"class":230},[220,670,671,674,676,679],{"class":222,"line":277},[220,672,673],{"class":270},"    \"panel se\"",[220,675,639],{"class":230},[220,677,678],{"class":270},"\"night\"",[220,680,645],{"class":230},[220,682,683],{"class":222,"line":293},[220,684,685],{"class":230},"}\n",[220,687,688],{"class":222,"line":298},[220,689,244],{"emptyLinePlaceholder":243},[220,691,692,695,697,699,702],{"class":222,"line":310},[220,693,694],{"class":230},"reference ",[220,696,253],{"class":226},[220,698,285],{"class":230},[220,700,701],{"class":270},"\"panel nw\"",[220,703,274],{"class":230},[220,705,706,709,712,715,718],{"class":222,"line":321},[220,707,708],{"class":226},"for",[220,710,711],{"class":230}," item_id, theme_name ",[220,713,714],{"class":226},"in",[220,716,717],{"class":304}," PANELS",[220,719,720],{"class":230},".items():\n",[220,722,724,727,729],{"class":222,"line":723},10,[220,725,726],{"class":230},"    item ",[220,728,253],{"class":226},[220,730,731],{"class":230}," layout.itemById(item_id)\n",[220,733,735,738,740],{"class":222,"line":734},11,[220,736,737],{"class":230},"    item.setFollowVisibilityPreset(",[220,739,305],{"class":304},[220,741,274],{"class":230},[220,743,745],{"class":222,"line":744},12,[220,746,747],{"class":230},"    item.setFollowVisibilityPresetName(theme_name)\n",[220,749,751],{"class":222,"line":750},13,[220,752,753],{"class":230},"    item.zoomToExtent(reference.extent())\n",[220,755,757],{"class":222,"line":756},14,[220,758,324],{"class":230},[14,760,761,763,764,767],{},[182,762,329],{}," Each map item keeps its own theme, its own extent and its own scale, so the four panels are genuinely independent objects that happen to share a page. ",[217,765,766],{},"zoomToExtent()"," copies the reference panel's extent to the others, which is what makes the comparison fair — four panels at four slightly different extents is the commonest defect in a small-multiples page and the hardest to notice. Note that the items must be the same aspect ratio for identical extents to produce identical framing; a panel of different proportions will pad the extent to fit.",[171,769,771],{"id":770},"per-item-overrides-for-the-exceptions","Per-item overrides, for the exceptions",[14,773,774],{},"Occasionally one item on the page must break the rule. An inset locator wants only the boundary and a highlight; a detail panel wants a layer that the layout's scale would normally hide. Layout map items carry their own overrides for exactly these cases.",[210,776,778],{"className":212,"code":777,"language":214,"meta":215,"style":215},"inset = layout.itemById(\"locator\")\ninset.setFollowVisibilityPreset(False)\ninset.setLayers([\n    project.mapLayersByName(\"boundaries\")[0],\n    project.mapLayersByName(\"study area\")[0],\n])\n",[217,779,780,794,804,809,825,838],{"__ignoreMap":215},[220,781,782,785,787,789,792],{"class":222,"line":223},[220,783,784],{"class":230},"inset ",[220,786,253],{"class":226},[220,788,285],{"class":230},[220,790,791],{"class":270},"\"locator\"",[220,793,274],{"class":230},[220,795,796,799,802],{"class":222,"line":240},[220,797,798],{"class":230},"inset.setFollowVisibilityPreset(",[220,800,801],{"class":304},"False",[220,803,274],{"class":230},[220,805,806],{"class":222,"line":247},[220,807,808],{"class":230},"inset.setLayers([\n",[220,810,811,814,817,820,822],{"class":222,"line":259},[220,812,813],{"class":230},"    project.mapLayersByName(",[220,815,816],{"class":270},"\"boundaries\"",[220,818,819],{"class":230},")[",[220,821,46],{"class":304},[220,823,824],{"class":230},"],\n",[220,826,827,829,832,834,836],{"class":222,"line":277},[220,828,813],{"class":230},[220,830,831],{"class":270},"\"study area\"",[220,833,819],{"class":230},[220,835,46],{"class":304},[220,837,824],{"class":230},[220,839,840],{"class":222,"line":293},[220,841,842],{"class":230},"])\n",[14,844,845,847,848,851],{},[182,846,329],{}," Turning the theme link off before setting a layer list matters — with the link on, the theme wins and the list is stored but unused, which produces an inset that stubbornly shows the main map's content. ",[217,849,850],{},"setLayers()"," takes layer objects in draw order, top of the list drawn last, and it is a complete list rather than an addition, so anything omitted is absent from that item regardless of the project state.",[14,853,854],{},"The scale-range override is a separate switch on the item, and it is worth knowing about because it explains a whole class of \"the layer is missing from one panel\" reports:",[210,856,858],{"className":212,"code":857,"language":214,"meta":215,"style":215},"detail = layout.itemById(\"panel se\")\ndetail.setFollowVisibilityPreset(True)\ndetail.setFollowVisibilityPresetName(\"land use\")\ndetail.setScale(5000)\n",[217,859,860,874,883,892],{"__ignoreMap":215},[220,861,862,865,867,869,872],{"class":222,"line":223},[220,863,864],{"class":230},"detail ",[220,866,253],{"class":226},[220,868,285],{"class":230},[220,870,871],{"class":270},"\"panel se\"",[220,873,274],{"class":230},[220,875,876,879,881],{"class":222,"line":240},[220,877,878],{"class":230},"detail.setFollowVisibilityPreset(",[220,880,305],{"class":304},[220,882,274],{"class":230},[220,884,885,888,890],{"class":222,"line":247},[220,886,887],{"class":230},"detail.setFollowVisibilityPresetName(",[220,889,666],{"class":270},[220,891,274],{"class":230},[220,893,894,897,900],{"class":222,"line":259},[220,895,896],{"class":230},"detail.setScale(",[220,898,899],{"class":304},"5000",[220,901,274],{"class":230},[14,903,904,906,907,911,912,915],{},[182,905,329],{}," Setting the scale explicitly recomputes the item's extent about its current centre, which is usually what a fixed-scale detail panel wants. Because ",[21,908,910],{"href":909},"\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fset-scale-based-visibility-pyqgis\u002F","scale-based visibility"," is evaluated against the ",[198,913,914],{},"item's"," scale rather than the canvas scale, a panel set to 1:5 000 shows layers that are hidden in the main 1:120 000 map — correct behaviour that reliably surprises people the first time.",[171,917,919],{"id":918},"making-the-whole-thing-reproducible","Making the whole thing reproducible",[14,921,922],{},"The value of themes in layouts is that a deliverable becomes a function of the project rather than of what somebody had ticked at the time. That only holds if the wiring itself is scripted.",[14,924,925],{},"Keep one build script that creates the themes, sets the item links, and exports — in that order, in one file, run from a clean project open. Anything clicked into place between runs is a difference nobody will remember six months later, and a layout whose map item quietly reverted to following the canvas produces four identical PDFs with four different names. Checking the state before exporting costs three lines and catches exactly that:",[210,927,929],{"className":212,"code":928,"language":214,"meta":215,"style":215},"assert map_item.followVisibilityPreset(), \"map item is not following a theme\"\nassert map_item.followVisibilityPresetName() in project.mapThemeCollection().mapThemes()\n",[217,930,931,942],{"__ignoreMap":215},[220,932,933,936,939],{"class":222,"line":223},[220,934,935],{"class":226},"assert",[220,937,938],{"class":230}," map_item.followVisibilityPreset(), ",[220,940,941],{"class":270},"\"map item is not following a theme\"\n",[220,943,944,946,949,951],{"class":222,"line":240},[220,945,935],{"class":226},[220,947,948],{"class":230}," map_item.followVisibilityPresetName() ",[220,950,714],{"class":226},[220,952,953],{"class":230}," project.mapThemeCollection().mapThemes()\n",[14,955,956,958],{},[182,957,329],{}," The second assertion catches a theme that was renamed or deleted after the layout was wired up, which leaves the item following a name that no longer resolves — QGIS falls back to the canvas silently rather than complaining. Both checks are cheap enough to leave in permanently.",[171,960,962],{"id":961},"export-the-series","Export the series",[14,964,965],{},"With the item linked, producing the set is a loop.",[210,967,969],{"className":212,"code":968,"language":214,"meta":215,"style":215},"from qgis.core import QgsLayoutExporter\n\nsettings = QgsLayoutExporter.PdfExportSettings()\nsettings.dpi = 300\n\nfor theme_name in project.mapThemeCollection().mapThemes():\n    map_item.setFollowVisibilityPresetName(theme_name)\n    layout.refresh()\n    exporter = QgsLayoutExporter(layout)\n    safe = theme_name.replace(\" \", \"_\").replace(\"\u002F\", \"-\")\n    result = exporter.exportToPdf(f\"\u002Fdata\u002Foutput\u002F{safe}.pdf\", settings)\n    if result != QgsLayoutExporter.Success:\n        raise RuntimeError(f\"export failed for {theme_name} with code {result}\")\n",[217,970,971,982,986,996,1006,1010,1022,1027,1032,1042,1074,1105,1119],{"__ignoreMap":215},[220,972,973,975,977,979],{"class":222,"line":223},[220,974,227],{"class":226},[220,976,231],{"class":230},[220,978,234],{"class":226},[220,980,981],{"class":230}," QgsLayoutExporter\n",[220,983,984],{"class":222,"line":240},[220,985,244],{"emptyLinePlaceholder":243},[220,987,988,991,993],{"class":222,"line":247},[220,989,990],{"class":230},"settings ",[220,992,253],{"class":226},[220,994,995],{"class":230}," QgsLayoutExporter.PdfExportSettings()\n",[220,997,998,1001,1003],{"class":222,"line":259},[220,999,1000],{"class":230},"settings.dpi ",[220,1002,253],{"class":226},[220,1004,1005],{"class":304}," 300\n",[220,1007,1008],{"class":222,"line":277},[220,1009,244],{"emptyLinePlaceholder":243},[220,1011,1012,1014,1017,1019],{"class":222,"line":293},[220,1013,708],{"class":226},[220,1015,1016],{"class":230}," theme_name ",[220,1018,714],{"class":226},[220,1020,1021],{"class":230}," project.mapThemeCollection().mapThemes():\n",[220,1023,1024],{"class":222,"line":298},[220,1025,1026],{"class":230},"    map_item.setFollowVisibilityPresetName(theme_name)\n",[220,1028,1029],{"class":222,"line":310},[220,1030,1031],{"class":230},"    layout.refresh()\n",[220,1033,1034,1037,1039],{"class":222,"line":321},[220,1035,1036],{"class":230},"    exporter ",[220,1038,253],{"class":226},[220,1040,1041],{"class":230}," QgsLayoutExporter(layout)\n",[220,1043,1044,1047,1049,1052,1055,1058,1061,1064,1067,1069,1072],{"class":222,"line":723},[220,1045,1046],{"class":230},"    safe ",[220,1048,253],{"class":226},[220,1050,1051],{"class":230}," theme_name.replace(",[220,1053,1054],{"class":270},"\" \"",[220,1056,1057],{"class":230},", ",[220,1059,1060],{"class":270},"\"_\"",[220,1062,1063],{"class":230},").replace(",[220,1065,1066],{"class":270},"\"\u002F\"",[220,1068,1057],{"class":230},[220,1070,1071],{"class":270},"\"-\"",[220,1073,274],{"class":230},[220,1075,1076,1079,1081,1084,1087,1090,1093,1096,1099,1102],{"class":222,"line":734},[220,1077,1078],{"class":230},"    result ",[220,1080,253],{"class":226},[220,1082,1083],{"class":230}," exporter.exportToPdf(",[220,1085,1086],{"class":226},"f",[220,1088,1089],{"class":270},"\"\u002Fdata\u002Foutput\u002F",[220,1091,1092],{"class":304},"{",[220,1094,1095],{"class":230},"safe",[220,1097,1098],{"class":304},"}",[220,1100,1101],{"class":270},".pdf\"",[220,1103,1104],{"class":230},", settings)\n",[220,1106,1107,1110,1113,1116],{"class":222,"line":744},[220,1108,1109],{"class":226},"    if",[220,1111,1112],{"class":230}," result ",[220,1114,1115],{"class":226},"!=",[220,1117,1118],{"class":230}," QgsLayoutExporter.Success:\n",[220,1120,1121,1124,1127,1129,1131,1134,1136,1139,1141,1144,1146,1149,1151,1154],{"class":222,"line":750},[220,1122,1123],{"class":226},"        raise",[220,1125,1126],{"class":304}," RuntimeError",[220,1128,397],{"class":230},[220,1130,1086],{"class":226},[220,1132,1133],{"class":270},"\"export failed for ",[220,1135,1092],{"class":304},[220,1137,1138],{"class":230},"theme_name",[220,1140,1098],{"class":304},[220,1142,1143],{"class":270}," with code ",[220,1145,1092],{"class":304},[220,1147,1148],{"class":230},"result",[220,1150,1098],{"class":304},[220,1152,1153],{"class":270},"\"",[220,1155,274],{"class":230},[14,1157,1158,1160,1161,1164,1165,1168,1169,1173],{},[182,1159,329],{}," A fresh ",[217,1162,1163],{},"QgsLayoutExporter"," per iteration avoids any state carried between exports, and it is cheap. Checking the return code matters because ",[217,1166,1167],{},"exportToPdf()"," does not raise — a full disk or an unwritable directory returns a status and the loop otherwise continues happily producing nothing. Sanitising the filename centrally is a small thing that prevents a theme named with a slash from writing into a directory that does not exist. See ",[21,1170,1172],{"href":1171},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002F","exporting multiple layouts"," for the variations on the export settings.",[171,1175,1177],{"id":1176},"qgis-version-compatibility","QGIS version compatibility",[14,1179,1180,1181,1183],{},"The examples target ",[182,1182,184],{}," (Python 3.12).",[1185,1186,1187,1203],"table",{},[1188,1189,1190],"thead",{},[1191,1192,1193,1197,1200],"tr",{},[1194,1195,1196],"th",{},"QGIS version",[1194,1198,1199],{},"Python",[1194,1201,1202],{},"Notes",[1204,1205,1206,1221,1238,1248,1259],"tbody",{},[1191,1207,1208,1212,1215],{},[1209,1210,1211],"td",{},"3.16 LTR",[1209,1213,1214],{},"3.7",[1209,1216,1217,1220],{},[217,1218,1219],{},"setFollowVisibilityPreset"," and the legend link settings all present.",[1191,1222,1223,1226,1229],{},[1209,1224,1225],{},"3.22 LTR",[1209,1227,1228],{},"3.9",[1209,1230,1231,1234,1235,193],{},[217,1232,1233],{},"QgsLayoutExporter.PdfExportSettings"," gains ",[217,1236,1237],{},"simplifyGeometries",[1191,1239,1240,1243,1245],{},[1209,1241,1242],{},"3.28 LTR",[1209,1244,1228],{},[1209,1246,1247],{},"Layout map items can override layer scale ranges per item.",[1191,1249,1250,1253,1256],{},[1209,1251,1252],{},"3.34 LTR",[1209,1254,1255],{},"3.12",[1209,1257,1258],{},"Baseline for this page.",[1191,1260,1261,1264,1266],{},[1209,1262,1263],{},"3.40+",[1209,1265,1255],{},[1209,1267,1268],{},"Theme-linked legend patch shapes follow the theme's styles.",[171,1270,1272],{"id":1271},"troubleshooting","Troubleshooting",[176,1274,1275,1284,1292,1300,1310,1316],{},[179,1276,1277,480,1280,1283],{},[182,1278,1279],{},"The map still shows the canvas.",[217,1281,1282],{},"setFollowVisibilityPreset(True)"," was not called; the name alone does nothing.",[179,1285,1286,480,1289,1291],{},[182,1287,1288],{},"The export shows the previous theme.",[217,1290,341],{}," was skipped between setting the name and exporting.",[179,1293,1294,1299],{},[182,1295,1296,1298],{},[217,1297,347],{}," returns None."," The item has no id set in its properties. Set one rather than iterating by type.",[179,1301,1302,1305,1306,1309],{},[182,1303,1304],{},"The legend lists layers the map does not show."," The legend is not linked to the map item, or ",[217,1307,1308],{},"setAutoUpdateModel(False)"," is preserving a stale hand-edited model.",[179,1311,1312,1315],{},[182,1313,1314],{},"Panels are framed slightly differently."," The items have different aspect ratios, so a shared extent is padded differently in each. Match the item sizes.",[179,1317,1318,1321,1322,193],{},[182,1319,1320],{},"The export \"succeeded\" but wrote nothing."," The return code was not checked. Compare against ",[217,1323,1324],{},"QgsLayoutExporter.Success",[171,1326,1328],{"id":1327},"conclusion","Conclusion",[14,1330,1331,1332,1334],{},"Set both ",[217,1333,1282],{}," and the theme name, refresh the layout before every export, and link the legend to the map item so it describes what is actually drawn. From there, one template plus the project's theme list generates a whole series, and the only per-deliverable code is a filename.",[171,1336,1338],{"id":1337},"frequently-asked-questions","Frequently Asked Questions",[14,1340,1341,1344],{},[182,1342,1343],{},"Can an atlas and a theme be used together?","\nYes, and they are orthogonal — the atlas drives the extent from a coverage feature while the theme drives the content. Looping over themes with an atlas inside produces the full cross product, which is the standard way to generate a set of thematic map books.",[14,1346,1347,1350,1351,1353],{},[182,1348,1349],{},"Does the theme override the item's layer list?","\nYes. Following a theme takes precedence over ",[217,1352,850],{},", so setting both is confusing rather than additive. Pick one mechanism per item.",[14,1355,1356,1359,1360,193],{},[182,1357,1358],{},"Can different pages of one layout follow different themes?","\nA layout is a single page design; multiple pages share the same items unless you add separate map items. For genuinely different content per page, use an atlas or several layouts in the ",[21,1361,1363],{"href":1362},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002F","layout manager",[14,1365,1366,1369],{},[182,1367,1368],{},"Will the theme's styles show in an exported legend?","\nYes, when the legend is linked to the map item and auto-updating, the patches are drawn with the styles the theme pinned rather than the layers' current styles.",[171,1371,1373],{"id":1372},"related","Related",[176,1375,1376,1381,1386,1392,1398],{},[179,1377,1378,1380],{},[21,1379,24],{"href":23}," — the guide this recipe belongs to",[179,1382,1383],{},[21,1384,1385],{"href":191},"Create a Map Theme in PyQGIS",[179,1387,1388],{},[21,1389,1391],{"href":1390},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-legend-to-layout-pyqgis\u002F","Add a Legend to a Layout in PyQGIS",[179,1393,1394],{},[21,1395,1397],{"href":1396},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-map-item-and-set-extent-pyqgis\u002F","Add a Map Item and Set Its Extent in PyQGIS",[179,1399,1400],{},[21,1401,1402],{"href":1171},"Exporting Multiple QGIS Layouts to PDF",[1404,1405,1406],"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);}",{"title":215,"searchDepth":240,"depth":240,"links":1408},[1409,1410,1411,1412,1413,1414,1415,1416,1417,1418,1419,1420],{"id":173,"depth":240,"text":174},{"id":204,"depth":240,"text":205},{"id":421,"depth":240,"text":422},{"id":611,"depth":240,"text":612},{"id":770,"depth":240,"text":771},{"id":918,"depth":240,"text":919},{"id":961,"depth":240,"text":962},{"id":1176,"depth":240,"text":1177},{"id":1271,"depth":240,"text":1272},{"id":1327,"depth":240,"text":1328},{"id":1337,"depth":240,"text":1338},{"id":1372,"depth":240,"text":1373},"Make a print layout follow a map theme from Python — setFollowVisibilityPreset, per-item layer overrides, theme-linked legends, and exporting one template as a series of deliverables.","md",{"slug":1424,"type":1425,"breadcrumb":1426,"datePublished":1427,"dateModified":1427},"apply-map-theme-to-layout-map-pyqgis","article","Themes in Layouts","2026-08-27","\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fapply-map-theme-to-layout-map-pyqgis",{"title":5,"description":1421},"pyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fapply-map-theme-to-layout-map-pyqgis\u002Findex","05NRD2zdRkek6REKbQlN0vN6ZdX3wmDo_xRACtj_ii8",1787823360559]