[{"data":1,"prerenderedAt":2106},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl":2078,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl":2086,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl":2091,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl":2092},{"id":4,"title":5,"body":6,"dateModified":2043,"datePublished":2043,"description":2044,"extension":2045,"faq":2046,"keyword":2055,"kind":2056,"meta":2057,"navigation":347,"path":2067,"seo":2068,"slug":2071,"stem":2072,"topics":2073,"type":2076,"__hash__":2077},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl\u002Findex.md","Add Icon Sets to Excel Cells with openpyxl",{"type":7,"value":8,"toc":2029},"minimark",[9,24,273,278,303,306,617,621,626,797,800,804,807,868,875,879,882,976,999,1125,1129,1132,1189,1192,1196,1211,1404,1417,1421,1424,1570,1577,1735,1739,1742,1879,1886,1890,1942,1946,1960,1964,1974,1980,1986,1992,1996,2025],[10,11,12,13,17,18,23],"p",{},"A column of percentages tells a reader little at a glance; the same column with a green, amber or red light beside each value tells them where to look. Excel's icon sets are conditional formatting rules, so they stay live: when the numbers change, the icons follow. openpyxl writes them with ",[14,15,16],"code",{},"IconSetRule",". This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002F","Applying Conditional Formatting with openpyxl",", covers how icon thresholds really work — the part that makes icons appear in the wrong colour when misunderstood — and how to keep targets editable by the people who read the report.",[25,26,34,35,34,39,34,43,34,51,34,60,34,64,34,71,34,75,34,79,34,83,34,87,34,91,34,95,34,99,34,103,34,107,34,112,34,114,34,118,34,120,34,123,34,125,34,127,34,130,34,132,34,135,34,138,34,142,34,144,34,148,34,153,34,157,34,159,34,161,34,164,34,166,34,169,34,172,34,176,34,178,34,181,34,185,34,189,34,191,34,193,34,195,34,198,34,202,34,204,34,207,34,211,34,215,34,217,34,219,34,221,34,224,34,228,34,230,34,233,34,235,34,238,34,240,34,242,34,244,34,247,34,251,34,253,34,256,34,258,34,261,34,263,34,265,34,267],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"153 0 454 250","img",[30,31],"is-sheet-t","is-sheet-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:454px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"Store attainment with traffic lights driven by target cells",[40,41,42],"desc",{"id":31},"Five stores have attainment of 95, 80, 62, 40 and 110 percent. With amber from 70 percent and green from 90 percent, stored in cells D2 and E2, Leeds and Derby show green lights, York amber, and Hull and Bath red.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"153","0","454","250","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"169.2","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[44,61],{"x":62,"y":54,"width":63,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"201.2","61.3",[65,66,70],"text",{"x":67,"y":68,"style":69},"231.8","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[44,72],{"x":73,"y":54,"width":74,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"262.5","92.7",[65,76,78],{"x":77,"y":68,"style":69},"308.8","B",[44,80],{"x":81,"y":54,"width":82,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"355.2","44.0",[65,84,86],{"x":85,"y":68,"style":69},"377.2","C",[44,88],{"x":89,"y":54,"width":90,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"399.2","97.8",[65,92,94],{"x":93,"y":68,"style":69},"448.1","D",[44,96],{"x":97,"y":54,"width":98,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"497.0","93.8",[65,100,102],{"x":101,"y":68,"style":69},"543.9","E",[44,104],{"x":53,"y":105,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"40.0","28.0",[65,108,111],{"x":109,"y":110,"style":69},"185.2","59.0","1",[44,113],{"x":62,"y":105,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,115,117],{"x":67,"y":110,"style":116},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Store",[44,119],{"x":73,"y":105,"width":74,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,121,122],{"x":77,"y":110,"style":116},"Attainment",[44,124],{"x":81,"y":105,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,126],{"x":89,"y":105,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,128,129],{"x":93,"y":110,"style":116},"Amber from",[44,131],{"x":97,"y":105,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,133,134],{"x":101,"y":110,"style":116},"Green from",[44,136],{"x":53,"y":137,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"68.0",[65,139,141],{"x":109,"y":140,"style":69},"87.0","2",[44,143],{"x":62,"y":137,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,145,147],{"x":67,"y":140,"style":146},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Leeds",[44,149],{"x":73,"y":137,"width":74,"height":106,"rx":47,"fill":150,"stroke":151,"style":152},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[65,154,156],{"x":77,"y":140,"style":155},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","● 95%",[44,158],{"x":81,"y":137,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,160],{"x":89,"y":137,"width":90,"height":106,"rx":47,"fill":57,"stroke":58,"style":152},[65,162,163],{"x":93,"y":140,"style":116},"70%",[44,165],{"x":97,"y":137,"width":98,"height":106,"rx":47,"fill":57,"stroke":58,"style":152},[65,167,168],{"x":101,"y":140,"style":116},"90%",[44,170],{"x":53,"y":171,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"96.0",[65,173,175],{"x":109,"y":174,"style":69},"115.0","3",[44,177],{"x":62,"y":171,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,179,180],{"x":67,"y":174,"style":146},"York",[44,182],{"x":73,"y":171,"width":74,"height":106,"rx":47,"fill":183,"stroke":184,"style":152},"#fdefd8","var(--gold,#b4740a)",[65,186,188],{"x":77,"y":174,"style":187},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","● 80%",[44,190],{"x":81,"y":171,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,192],{"x":89,"y":171,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,194],{"x":97,"y":171,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,196],{"x":53,"y":197,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"124.0",[65,199,201],{"x":109,"y":200,"style":69},"143.0","4",[44,203],{"x":62,"y":197,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,205,206],{"x":67,"y":200,"style":146},"Hull",[44,208],{"x":73,"y":197,"width":74,"height":106,"rx":47,"fill":209,"stroke":210,"style":152},"#fee8f2","var(--accent,#f43f8f)",[65,212,214],{"x":77,"y":200,"style":213},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","● 62%",[44,216],{"x":81,"y":197,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,218],{"x":89,"y":197,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,220],{"x":97,"y":197,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,222],{"x":53,"y":223,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"152.0",[65,225,227],{"x":109,"y":226,"style":69},"171.0","5",[44,229],{"x":62,"y":223,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,231,232],{"x":67,"y":226,"style":146},"Bath",[44,234],{"x":73,"y":223,"width":74,"height":106,"rx":47,"fill":209,"stroke":210,"style":152},[65,236,237],{"x":77,"y":226,"style":213},"● 40%",[44,239],{"x":81,"y":223,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,241],{"x":89,"y":223,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,243],{"x":97,"y":223,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,245],{"x":53,"y":246,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"180.0",[65,248,250],{"x":109,"y":249,"style":69},"199.0","6",[44,252],{"x":62,"y":246,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,254,255],{"x":67,"y":249,"style":146},"Derby",[44,257],{"x":73,"y":246,"width":74,"height":106,"rx":47,"fill":150,"stroke":151,"style":152},[65,259,260],{"x":77,"y":249,"style":155},"● 110%",[44,262],{"x":81,"y":246,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,264],{"x":89,"y":246,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[44,266],{"x":97,"y":246,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,268,272],{"x":269,"y":270,"style":271},"380.0","230.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the icon rule reads its thresholds from D2 and E2",[274,275,277],"h2",{"id":276},"prerequisites","Prerequisites",[279,280,285],"pre",{"className":281,"code":282,"language":283,"meta":284,"style":284},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[14,286,287],{"__ignoreMap":284},[288,289,292,296,300],"span",{"class":290,"line":291},"line",1,[288,293,295],{"class":294},"sMTad","pip",[288,297,299],{"class":298},"srMev"," install",[288,301,302],{"class":298}," openpyxl\n",[10,304,305],{},"A small attainment report, with the targets in their own cells:",[279,307,311],{"className":308,"code":309,"language":310,"meta":284,"style":284},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook, load_workbook\nfrom openpyxl.formatting.rule import IconSetRule\n\nwb = Workbook()\nws = wb.active\nws.title = \"Attainment\"\nws.append([\"Store\", \"Attainment\", \"\", \"Amber from\", \"Green from\"])\nfor store, value in [(\"Leeds\", 0.95), (\"York\", 0.80), (\"Hull\", 0.62), (\"Bath\", 0.40), (\"Derby\", 1.10)]:\n    ws.append([store, value])\nws[\"D2\"], ws[\"E2\"] = 0.70, 0.90\nfor cell in ws[\"B\"][1:]:\n    cell.number_format = \"0%\"\nfor col in \"DE\":\n    ws[f\"{col}2\"].number_format = \"0%\"\nws.column_dimensions[\"B\"].width = 14\n","python",[14,312,313,329,342,349,361,372,383,416,484,490,518,542,553,569,601],{"__ignoreMap":284},[288,314,315,319,323,326],{"class":290,"line":291},[288,316,318],{"class":317},"s-kum","from",[288,320,322],{"class":321},"skGVy"," openpyxl ",[288,324,325],{"class":317},"import",[288,327,328],{"class":321}," Workbook, load_workbook\n",[288,330,332,334,337,339],{"class":290,"line":331},2,[288,333,318],{"class":317},[288,335,336],{"class":321}," openpyxl.formatting.rule ",[288,338,325],{"class":317},[288,340,341],{"class":321}," IconSetRule\n",[288,343,345],{"class":290,"line":344},3,[288,346,348],{"emptyLinePlaceholder":347},true,"\n",[288,350,352,355,358],{"class":290,"line":351},4,[288,353,354],{"class":321},"wb ",[288,356,357],{"class":317},"=",[288,359,360],{"class":321}," Workbook()\n",[288,362,364,367,369],{"class":290,"line":363},5,[288,365,366],{"class":321},"ws ",[288,368,357],{"class":317},[288,370,371],{"class":321}," wb.active\n",[288,373,375,378,380],{"class":290,"line":374},6,[288,376,377],{"class":321},"ws.title ",[288,379,357],{"class":317},[288,381,382],{"class":298}," \"Attainment\"\n",[288,384,386,389,392,395,398,400,403,405,408,410,413],{"class":290,"line":385},7,[288,387,388],{"class":321},"ws.append([",[288,390,391],{"class":298},"\"Store\"",[288,393,394],{"class":321},", ",[288,396,397],{"class":298},"\"Attainment\"",[288,399,394],{"class":321},[288,401,402],{"class":298},"\"\"",[288,404,394],{"class":321},[288,406,407],{"class":298},"\"Amber from\"",[288,409,394],{"class":321},[288,411,412],{"class":298},"\"Green from\"",[288,414,415],{"class":321},"])\n",[288,417,419,422,425,428,431,434,436,440,443,446,448,451,453,456,458,461,463,466,468,471,473,476,478,481],{"class":290,"line":418},8,[288,420,421],{"class":317},"for",[288,423,424],{"class":321}," store, value ",[288,426,427],{"class":317},"in",[288,429,430],{"class":321}," [(",[288,432,433],{"class":298},"\"Leeds\"",[288,435,394],{"class":321},[288,437,439],{"class":438},"sP0c6","0.95",[288,441,442],{"class":321},"), (",[288,444,445],{"class":298},"\"York\"",[288,447,394],{"class":321},[288,449,450],{"class":438},"0.80",[288,452,442],{"class":321},[288,454,455],{"class":298},"\"Hull\"",[288,457,394],{"class":321},[288,459,460],{"class":438},"0.62",[288,462,442],{"class":321},[288,464,465],{"class":298},"\"Bath\"",[288,467,394],{"class":321},[288,469,470],{"class":438},"0.40",[288,472,442],{"class":321},[288,474,475],{"class":298},"\"Derby\"",[288,477,394],{"class":321},[288,479,480],{"class":438},"1.10",[288,482,483],{"class":321},")]:\n",[288,485,487],{"class":290,"line":486},9,[288,488,489],{"class":321},"    ws.append([store, value])\n",[288,491,493,496,499,502,505,508,510,513,515],{"class":290,"line":492},10,[288,494,495],{"class":321},"ws[",[288,497,498],{"class":298},"\"D2\"",[288,500,501],{"class":321},"], ws[",[288,503,504],{"class":298},"\"E2\"",[288,506,507],{"class":321},"] ",[288,509,357],{"class":317},[288,511,512],{"class":438}," 0.70",[288,514,394],{"class":321},[288,516,517],{"class":438},"0.90\n",[288,519,521,523,526,528,531,534,537,539],{"class":290,"line":520},11,[288,522,421],{"class":317},[288,524,525],{"class":321}," cell ",[288,527,427],{"class":317},[288,529,530],{"class":321}," ws[",[288,532,533],{"class":298},"\"B\"",[288,535,536],{"class":321},"][",[288,538,111],{"class":438},[288,540,541],{"class":321},":]:\n",[288,543,545,548,550],{"class":290,"line":544},12,[288,546,547],{"class":321},"    cell.number_format ",[288,549,357],{"class":317},[288,551,552],{"class":298}," \"0%\"\n",[288,554,556,558,561,563,566],{"class":290,"line":555},13,[288,557,421],{"class":317},[288,559,560],{"class":321}," col ",[288,562,427],{"class":317},[288,564,565],{"class":298}," \"DE\"",[288,567,568],{"class":321},":\n",[288,570,572,575,578,581,585,588,591,594,597,599],{"class":290,"line":571},14,[288,573,574],{"class":321},"    ws[",[288,576,577],{"class":317},"f",[288,579,580],{"class":298},"\"",[288,582,584],{"class":583},"sSjpA","{",[288,586,587],{"class":321},"col",[288,589,590],{"class":583},"}",[288,592,593],{"class":298},"2\"",[288,595,596],{"class":321},"].number_format ",[288,598,357],{"class":317},[288,600,552],{"class":298},[288,602,604,607,609,612,614],{"class":290,"line":603},15,[288,605,606],{"class":321},"ws.column_dimensions[",[288,608,533],{"class":298},[288,610,611],{"class":321},"].width ",[288,613,357],{"class":317},[288,615,616],{"class":438}," 14\n",[274,618,620],{"id":619},"step-1-pick-an-icon-set","Step 1 — Pick an icon set",[10,622,623,625],{},[14,624,16],{}," takes the name of one of Excel's built-in sets. The number in the name is the number of icons, and it must match the number of thresholds you pass:",[279,627,629],{"className":308,"code":628,"language":310,"meta":284,"style":284},"ICON_SETS = {\n    3: [\"3Arrows\", \"3ArrowsGray\", \"3Flags\", \"3TrafficLights1\", \"3TrafficLights2\", \"3Signs\", \"3Symbols\", \"3Symbols2\"],\n    4: [\"4Arrows\", \"4ArrowsGray\", \"4RedToBlack\", \"4Rating\", \"4TrafficLights\"],\n    5: [\"5Arrows\", \"5ArrowsGray\", \"5Quarters\", \"5Rating\"],\n}\nprint(sum(len(v) for v in ICON_SETS.values()), \"built-in sets\")      # 17 built-in sets\n",[14,630,631,642,691,723,750,755],{"__ignoreMap":284},[288,632,633,636,639],{"class":290,"line":291},[288,634,635],{"class":438},"ICON_SETS",[288,637,638],{"class":317}," =",[288,640,641],{"class":321}," {\n",[288,643,644,647,650,653,655,658,660,663,665,668,670,673,675,678,680,683,685,688],{"class":290,"line":331},[288,645,646],{"class":438},"    3",[288,648,649],{"class":321},": [",[288,651,652],{"class":298},"\"3Arrows\"",[288,654,394],{"class":321},[288,656,657],{"class":298},"\"3ArrowsGray\"",[288,659,394],{"class":321},[288,661,662],{"class":298},"\"3Flags\"",[288,664,394],{"class":321},[288,666,667],{"class":298},"\"3TrafficLights1\"",[288,669,394],{"class":321},[288,671,672],{"class":298},"\"3TrafficLights2\"",[288,674,394],{"class":321},[288,676,677],{"class":298},"\"3Signs\"",[288,679,394],{"class":321},[288,681,682],{"class":298},"\"3Symbols\"",[288,684,394],{"class":321},[288,686,687],{"class":298},"\"3Symbols2\"",[288,689,690],{"class":321},"],\n",[288,692,693,696,698,701,703,706,708,711,713,716,718,721],{"class":290,"line":344},[288,694,695],{"class":438},"    4",[288,697,649],{"class":321},[288,699,700],{"class":298},"\"4Arrows\"",[288,702,394],{"class":321},[288,704,705],{"class":298},"\"4ArrowsGray\"",[288,707,394],{"class":321},[288,709,710],{"class":298},"\"4RedToBlack\"",[288,712,394],{"class":321},[288,714,715],{"class":298},"\"4Rating\"",[288,717,394],{"class":321},[288,719,720],{"class":298},"\"4TrafficLights\"",[288,722,690],{"class":321},[288,724,725,728,730,733,735,738,740,743,745,748],{"class":290,"line":351},[288,726,727],{"class":438},"    5",[288,729,649],{"class":321},[288,731,732],{"class":298},"\"5Arrows\"",[288,734,394],{"class":321},[288,736,737],{"class":298},"\"5ArrowsGray\"",[288,739,394],{"class":321},[288,741,742],{"class":298},"\"5Quarters\"",[288,744,394],{"class":321},[288,746,747],{"class":298},"\"5Rating\"",[288,749,690],{"class":321},[288,751,752],{"class":290,"line":363},[288,753,754],{"class":321},"}\n",[288,756,757,760,763,766,768,771,774,776,779,781,784,787,790,793],{"class":290,"line":374},[288,758,759],{"class":438},"print",[288,761,762],{"class":321},"(",[288,764,765],{"class":438},"sum",[288,767,762],{"class":321},[288,769,770],{"class":438},"len",[288,772,773],{"class":321},"(v) ",[288,775,421],{"class":317},[288,777,778],{"class":321}," v ",[288,780,427],{"class":317},[288,782,783],{"class":438}," ICON_SETS",[288,785,786],{"class":321},".values()), ",[288,788,789],{"class":298},"\"built-in sets\"",[288,791,792],{"class":321},")      ",[288,794,796],{"class":795},"s-wDw","# 17 built-in sets\n",[10,798,799],{},"Traffic lights suit status against a target, arrows suit change (up, flat, down), and the rating and quarter sets suit a score. Pick the set that matches the question the column answers; three icons are easier to read than five. The newer sets Excel added later — stars, triangles, boxes — are stored in an extension that openpyxl does not write, so stay with the list above.",[274,801,803],{"id":802},"step-2-thresholds-are-lower-bounds","Step 2 — Thresholds are lower bounds",[10,805,806],{},"Each threshold is the lower bound of an icon's band, listed from the lowest icon (red) to the highest (green). The first value is the floor for the first icon and should be at or below the smallest possible value:",[279,808,810],{"className":308,"code":809,"language":310,"meta":284,"style":284},"rule = IconSetRule(\"3TrafficLights1\", \"num\", [0, 0.70, 0.90])\nprint([(c.type, c.val) for c in rule.iconSet.cfvo])\n# [('num', 0.0), ('num', 0.7), ('num', 0.9)]\n",[14,811,812,846,863],{"__ignoreMap":284},[288,813,814,817,819,822,824,826,829,832,834,836,839,841,844],{"class":290,"line":291},[288,815,816],{"class":321},"rule ",[288,818,357],{"class":317},[288,820,821],{"class":321}," IconSetRule(",[288,823,667],{"class":298},[288,825,394],{"class":321},[288,827,828],{"class":298},"\"num\"",[288,830,831],{"class":321},", [",[288,833,47],{"class":438},[288,835,394],{"class":321},[288,837,838],{"class":438},"0.70",[288,840,394],{"class":321},[288,842,843],{"class":438},"0.90",[288,845,415],{"class":321},[288,847,848,850,853,855,858,860],{"class":290,"line":331},[288,849,759],{"class":438},[288,851,852],{"class":321},"([(c.type, c.val) ",[288,854,421],{"class":317},[288,856,857],{"class":321}," c ",[288,859,427],{"class":317},[288,861,862],{"class":321}," rule.iconSet.cfvo])\n",[288,864,865],{"class":290,"line":344},[288,866,867],{"class":795},"# [('num', 0.0), ('num', 0.7), ('num', 0.9)]\n",[10,869,870,871,874],{},"Applied to the attainment column, values from 0.9 upwards get green, from 0.7 amber, and everything else red. By default each boundary is inclusive, so exactly 90% is green. The rule writes a cell's icon from its own value, which is why ",[14,872,873],{},"B2:B6"," must hold the numbers being judged, not labels.",[274,876,878],{"id":877},"step-3-num-percent-or-percentile","Step 3 — num, percent or percentile",[10,880,881],{},"The second argument decides what the thresholds mean:",[279,883,885],{"className":308,"code":884,"language":310,"meta":284,"style":284},"relative = IconSetRule(\"3Arrows\", \"percent\", [0, 33, 67])\nranked = IconSetRule(\"4Rating\", \"percentile\", [0, 25, 50, 75])\nprint(relative.iconSet.cfvo[1].type, ranked.iconSet.cfvo[3].val)     # percent 75.0\n",[14,886,887,919,956],{"__ignoreMap":284},[288,888,889,892,894,896,898,900,903,905,907,909,912,914,917],{"class":290,"line":291},[288,890,891],{"class":321},"relative ",[288,893,357],{"class":317},[288,895,821],{"class":321},[288,897,652],{"class":298},[288,899,394],{"class":321},[288,901,902],{"class":298},"\"percent\"",[288,904,831],{"class":321},[288,906,47],{"class":438},[288,908,394],{"class":321},[288,910,911],{"class":438},"33",[288,913,394],{"class":321},[288,915,916],{"class":438},"67",[288,918,415],{"class":321},[288,920,921,924,926,928,930,932,935,937,939,941,944,946,949,951,954],{"class":290,"line":331},[288,922,923],{"class":321},"ranked ",[288,925,357],{"class":317},[288,927,821],{"class":321},[288,929,715],{"class":298},[288,931,394],{"class":321},[288,933,934],{"class":298},"\"percentile\"",[288,936,831],{"class":321},[288,938,47],{"class":438},[288,940,394],{"class":321},[288,942,943],{"class":438},"25",[288,945,394],{"class":321},[288,947,948],{"class":438},"50",[288,950,394],{"class":321},[288,952,953],{"class":438},"75",[288,955,415],{"class":321},[288,957,958,960,963,965,968,970,973],{"class":290,"line":344},[288,959,759],{"class":438},[288,961,962],{"class":321},"(relative.iconSet.cfvo[",[288,964,111],{"class":438},[288,966,967],{"class":321},"].type, ranked.iconSet.cfvo[",[288,969,175],{"class":438},[288,971,972],{"class":321},"].val)     ",[288,974,975],{"class":795},"# percent 75.0\n",[10,977,978,979,982,983,986,987,990,991,993,994,998],{},"With ",[14,980,981],{},"num",", a value always gets the same icon. With ",[14,984,985],{},"percent",", the thresholds are positions between the range's minimum and maximum, so the icons describe where a value sits relative to the others — and change when one outlier arrives. With ",[14,988,989],{},"percentile",", they split the values into groups of roughly equal size. For performance against a fixed target, ",[14,992,981],{}," is almost always right; the relative types suit \"how does this compare with the rest\" displays, where a colour scale is often clearer anyway — see ",[19,995,997],{"href":996},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl\u002F","Add Data Bars and Colour Scales with openpyxl",".",[25,1000,34,1006,34,1009,34,1012,34,1016,34,1020,34,1027,34,1033,34,1036,34,1040,34,1044,34,1048,34,1050,34,1055,34,1057,34,1067,34,1070,34,1074,34,1076,34,1080,34,1082,34,1087,34,1089,34,1092,34,1094,34,1098,34,1100,34,1107,34,1110,34,1112,34,1114,34,1118,34,1120],{"viewBox":1001,"role":28,"ariaLabelledBy":1002,"xmlns":32,"style":1005},"4 0 752 208",[1003,1004],"is-types-t","is-types-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,1007,1008],{"id":1003},"What each threshold type compares against",[40,1010,1011],{"id":1004},"num compares each value with fixed numbers, so the same value always gets the same icon. percent places thresholds between the minimum and maximum of the range. percentile splits the values into groups of equal size. formula-style cell references let readers change the thresholds in the sheet.",[44,1013],{"x":201,"y":47,"width":1014,"height":1015,"fill":50},"752","208",[44,1017],{"x":1018,"y":54,"width":1019,"height":105,"rx":47,"fill":50,"stroke":58,"style":59},"20.0","139.7",[44,1021],{"x":1022,"y":54,"width":1023,"height":105,"rx":47,"fill":1024,"stroke":1025,"style":1026},"159.7","290.2","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[65,1028,1032],{"x":1029,"y":1030,"style":1031},"304.8","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Threshold means",[44,1034],{"x":1035,"y":54,"width":1023,"height":105,"rx":47,"fill":1024,"stroke":1025,"style":1026},"449.8",[65,1037,1039],{"x":1038,"y":1030,"style":1031},"594.9","Stable when data changes",[44,1041],{"x":1018,"y":1042,"width":1019,"height":1043,"rx":47,"fill":57,"stroke":58,"style":59},"56.0","34.0",[65,1045,981],{"x":55,"y":1046,"style":1047},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[44,1049],{"x":1022,"y":1042,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[65,1051,1054],{"x":1029,"y":1052,"style":1053},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","a fixed value",[44,1056],{"x":1035,"y":1042,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[1058,1059,1061],"g",{"transform":1060},"translate(585.9,64.0) scale(0.750)",[1062,1063],"path",{"d":1064,"fill":1065,"stroke":151,"style":1066},"M5 12.5l4.5 4.5L19 7","none","stroke-width:2.40px;stroke-linecap:round;stroke-linejoin:round",[44,1068],{"x":1018,"y":1069,"width":1019,"height":1043,"rx":47,"fill":57,"stroke":58,"style":59},"90.0",[65,1071,1073],{"x":55,"y":1072,"style":1047},"112.0","num with \"$D$2\"",[44,1075],{"x":1022,"y":1069,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[65,1077,1079],{"x":1029,"y":1078,"style":1053},"111.0","a value in a cell",[44,1081],{"x":1035,"y":1069,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[1058,1083,1085],{"transform":1084},"translate(585.9,98.0) scale(0.750)",[1062,1086],{"d":1064,"fill":1065,"stroke":151,"style":1066},[44,1088],{"x":1018,"y":197,"width":1019,"height":1043,"rx":47,"fill":57,"stroke":58,"style":59},[65,1090,985],{"x":55,"y":1091,"style":1047},"146.0",[44,1093],{"x":1022,"y":197,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[65,1095,1097],{"x":1029,"y":1096,"style":1053},"145.0","position between min and max",[44,1099],{"x":1035,"y":197,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[1058,1101,1103],{"transform":1102},"translate(586.9,133.0) scale(0.667)",[1062,1104],{"d":1105,"fill":1065,"stroke":210,"style":1106},"M6 6l12 12 M18 6L6 18","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[44,1108],{"x":1018,"y":1109,"width":1019,"height":1043,"rx":47,"fill":57,"stroke":58,"style":59},"158.0",[65,1111,989],{"x":55,"y":246,"style":1047},[44,1113],{"x":1022,"y":1109,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[65,1115,1117],{"x":1029,"y":1116,"style":1053},"179.0","rank among the values",[44,1119],{"x":1035,"y":1109,"width":1023,"height":1043,"rx":47,"fill":50,"stroke":58,"style":59},[1058,1121,1123],{"transform":1122},"translate(586.9,167.0) scale(0.667)",[1062,1124],{"d":1105,"fill":1065,"stroke":210,"style":1106},[274,1126,1128],{"id":1127},"step-4-let-readers-edit-the-targets","Step 4 — Let readers edit the targets",[10,1130,1131],{},"Hard-coded thresholds hide the target inside the rule, where nobody can see or change it. Pass cell references instead, and the rule reads the target from the sheet:",[279,1133,1135],{"className":308,"code":1134,"language":310,"meta":284,"style":284},"rule = IconSetRule(\"3TrafficLights1\", \"num\", [0, \"$D$2\", \"$E$2\"])\nws.conditional_formatting.add(\"B2:B6\", rule)\nwb.save(\"attainment.xlsx\")\n",[14,1136,1137,1167,1178],{"__ignoreMap":284},[288,1138,1139,1141,1143,1145,1147,1149,1151,1153,1155,1157,1160,1162,1165],{"class":290,"line":291},[288,1140,816],{"class":321},[288,1142,357],{"class":317},[288,1144,821],{"class":321},[288,1146,667],{"class":298},[288,1148,394],{"class":321},[288,1150,828],{"class":298},[288,1152,831],{"class":321},[288,1154,47],{"class":438},[288,1156,394],{"class":321},[288,1158,1159],{"class":298},"\"$D$2\"",[288,1161,394],{"class":321},[288,1163,1164],{"class":298},"\"$E$2\"",[288,1166,415],{"class":321},[288,1168,1169,1172,1175],{"class":290,"line":331},[288,1170,1171],{"class":321},"ws.conditional_formatting.add(",[288,1173,1174],{"class":298},"\"B2:B6\"",[288,1176,1177],{"class":321},", rule)\n",[288,1179,1180,1183,1186],{"class":290,"line":344},[288,1181,1182],{"class":321},"wb.save(",[288,1184,1185],{"class":298},"\"attainment.xlsx\"",[288,1187,1188],{"class":321},")\n",[10,1190,1191],{},"Use absolute references with dollar signs, so every cell in the range compares with the same targets. Rendering the saved file with LibreOffice shows Leeds and Derby green, York amber and Hull and Bath red; change D2 to 50% and Hull turns amber without anyone touching the rule. Put the targets on the report sheet, labelled, or on a settings sheet that report owners know about.",[274,1193,1195],{"id":1194},"step-5-exclusive-boundaries-reversed-order-and-icon-only-cells","Step 5 — Exclusive boundaries, reversed order and icon-only cells",[10,1197,1198,1199,1202,1203,1206,1207,1210],{},"Three switches cover most remaining needs. ",[14,1200,1201],{},"reverse=True"," swaps the icon order, for measures where low is good — defect rates, days overdue. ",[14,1204,1205],{},"showValue=False"," hides the number and shows only the icon. And an inclusive boundary can be made exclusive by setting ",[14,1208,1209],{},"gte=False"," on the threshold, so a value must be strictly greater than it:",[279,1212,1214],{"className":308,"code":1213,"language":310,"meta":284,"style":284},"defects = IconSetRule(\"3TrafficLights1\", \"num\", [0, 0.02, 0.05], reverse=True)\nstrict = IconSetRule(\"3TrafficLights1\", \"num\", [0, 0.70, 0.90], showValue=False)\nstrict.iconSet.cfvo[2].gte = False            # green only above 90%, not at exactly 90%\nfor row in range(2, 7):\n    ws[f\"C{row}\"] = f\"=B{row}\"                # an icon-only copy of the attainment\nws.conditional_formatting.add(\"C2:C6\", strict)\nprint(strict.iconSet.cfvo[2].gte)             # False\n",[14,1215,1216,1259,1298,1316,1340,1379,1389],{"__ignoreMap":284},[288,1217,1218,1221,1223,1225,1227,1229,1231,1233,1235,1237,1240,1242,1245,1248,1252,1254,1257],{"class":290,"line":291},[288,1219,1220],{"class":321},"defects ",[288,1222,357],{"class":317},[288,1224,821],{"class":321},[288,1226,667],{"class":298},[288,1228,394],{"class":321},[288,1230,828],{"class":298},[288,1232,831],{"class":321},[288,1234,47],{"class":438},[288,1236,394],{"class":321},[288,1238,1239],{"class":438},"0.02",[288,1241,394],{"class":321},[288,1243,1244],{"class":438},"0.05",[288,1246,1247],{"class":321},"], ",[288,1249,1251],{"class":1250},"sa561","reverse",[288,1253,357],{"class":317},[288,1255,1256],{"class":438},"True",[288,1258,1188],{"class":321},[288,1260,1261,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286,1288,1291,1293,1296],{"class":290,"line":331},[288,1262,1263],{"class":321},"strict ",[288,1265,357],{"class":317},[288,1267,821],{"class":321},[288,1269,667],{"class":298},[288,1271,394],{"class":321},[288,1273,828],{"class":298},[288,1275,831],{"class":321},[288,1277,47],{"class":438},[288,1279,394],{"class":321},[288,1281,838],{"class":438},[288,1283,394],{"class":321},[288,1285,843],{"class":438},[288,1287,1247],{"class":321},[288,1289,1290],{"class":1250},"showValue",[288,1292,357],{"class":317},[288,1294,1295],{"class":438},"False",[288,1297,1188],{"class":321},[288,1299,1300,1303,1305,1308,1310,1313],{"class":290,"line":344},[288,1301,1302],{"class":321},"strict.iconSet.cfvo[",[288,1304,141],{"class":438},[288,1306,1307],{"class":321},"].gte ",[288,1309,357],{"class":317},[288,1311,1312],{"class":438}," False",[288,1314,1315],{"class":795},"            # green only above 90%, not at exactly 90%\n",[288,1317,1318,1320,1323,1325,1328,1330,1332,1334,1337],{"class":290,"line":351},[288,1319,421],{"class":317},[288,1321,1322],{"class":321}," row ",[288,1324,427],{"class":317},[288,1326,1327],{"class":438}," range",[288,1329,762],{"class":321},[288,1331,141],{"class":438},[288,1333,394],{"class":321},[288,1335,1336],{"class":438},"7",[288,1338,1339],{"class":321},"):\n",[288,1341,1342,1344,1346,1349,1351,1354,1356,1358,1360,1362,1365,1368,1370,1372,1374,1376],{"class":290,"line":363},[288,1343,574],{"class":321},[288,1345,577],{"class":317},[288,1347,1348],{"class":298},"\"C",[288,1350,584],{"class":583},[288,1352,1353],{"class":321},"row",[288,1355,590],{"class":583},[288,1357,580],{"class":298},[288,1359,507],{"class":321},[288,1361,357],{"class":317},[288,1363,1364],{"class":317}," f",[288,1366,1367],{"class":298},"\"=B",[288,1369,584],{"class":583},[288,1371,1353],{"class":321},[288,1373,590],{"class":583},[288,1375,580],{"class":298},[288,1377,1378],{"class":795},"                # an icon-only copy of the attainment\n",[288,1380,1381,1383,1386],{"class":290,"line":374},[288,1382,1171],{"class":321},[288,1384,1385],{"class":298},"\"C2:C6\"",[288,1387,1388],{"class":321},", strict)\n",[288,1390,1391,1393,1396,1398,1401],{"class":290,"line":385},[288,1392,759],{"class":438},[288,1394,1395],{"class":321},"(strict.iconSet.cfvo[",[288,1397,141],{"class":438},[288,1399,1400],{"class":321},"].gte)             ",[288,1402,1403],{"class":795},"# False\n",[10,1405,1406,1408,1409,1412,1413,1416],{},[14,1407,16],{}," has no argument for ",[14,1410,1411],{},"gte",", but the threshold objects it creates are exposed in ",[14,1414,1415],{},"rule.iconSet.cfvo"," and can be adjusted before the rule is added. An icon-only column works well next to the labelled number, as in the sample: column C shows the verdict, column B the figure.",[274,1418,1420],{"id":1419},"step-6-icons-from-a-status-that-needs-a-calculation","Step 6 — Icons from a status that needs a calculation",[10,1422,1423],{},"Icon sets judge a cell by its own value. When the status depends on several columns — behind plan and falling, say — compute a status number in a helper column and put the icons on that column with the value hidden:",[279,1425,1427],{"className":308,"code":1426,"language":310,"meta":284,"style":284},"ws[\"G1\"] = \"Status\"\nfor row in range(2, 7):\n    ws[f\"G{row}\"] = f'=IF(B{row}>=$E$2,3,IF(B{row}>=$D$2,2,1))'\nws.conditional_formatting.add(\"G2:G6\", IconSetRule(\"3Symbols\", \"num\", [1, 2, 3], showValue=False))\nwb.save(\"attainment.xlsx\")\nprint(ws[\"G3\"].value)          # =IF(B3>=$E$2,3,IF(B3>=$D$2,2,1))\n",[14,1428,1429,1443,1463,1507,1546,1554],{"__ignoreMap":284},[288,1430,1431,1433,1436,1438,1440],{"class":290,"line":291},[288,1432,495],{"class":321},[288,1434,1435],{"class":298},"\"G1\"",[288,1437,507],{"class":321},[288,1439,357],{"class":317},[288,1441,1442],{"class":298}," \"Status\"\n",[288,1444,1445,1447,1449,1451,1453,1455,1457,1459,1461],{"class":290,"line":331},[288,1446,421],{"class":317},[288,1448,1322],{"class":321},[288,1450,427],{"class":317},[288,1452,1327],{"class":438},[288,1454,762],{"class":321},[288,1456,141],{"class":438},[288,1458,394],{"class":321},[288,1460,1336],{"class":438},[288,1462,1339],{"class":321},[288,1464,1465,1467,1469,1472,1474,1476,1478,1480,1482,1484,1486,1489,1491,1493,1495,1498,1500,1502,1504],{"class":290,"line":344},[288,1466,574],{"class":321},[288,1468,577],{"class":317},[288,1470,1471],{"class":298},"\"G",[288,1473,584],{"class":583},[288,1475,1353],{"class":321},[288,1477,590],{"class":583},[288,1479,580],{"class":298},[288,1481,507],{"class":321},[288,1483,357],{"class":317},[288,1485,1364],{"class":317},[288,1487,1488],{"class":298},"'=IF(B",[288,1490,584],{"class":583},[288,1492,1353],{"class":321},[288,1494,590],{"class":583},[288,1496,1497],{"class":298},">=$E$2,3,IF(B",[288,1499,584],{"class":583},[288,1501,1353],{"class":321},[288,1503,590],{"class":583},[288,1505,1506],{"class":298},">=$D$2,2,1))'\n",[288,1508,1509,1511,1514,1517,1519,1521,1523,1525,1527,1529,1531,1533,1535,1537,1539,1541,1543],{"class":290,"line":351},[288,1510,1171],{"class":321},[288,1512,1513],{"class":298},"\"G2:G6\"",[288,1515,1516],{"class":321},", IconSetRule(",[288,1518,682],{"class":298},[288,1520,394],{"class":321},[288,1522,828],{"class":298},[288,1524,831],{"class":321},[288,1526,111],{"class":438},[288,1528,394],{"class":321},[288,1530,141],{"class":438},[288,1532,394],{"class":321},[288,1534,175],{"class":438},[288,1536,1247],{"class":321},[288,1538,1290],{"class":1250},[288,1540,357],{"class":317},[288,1542,1295],{"class":438},[288,1544,1545],{"class":321},"))\n",[288,1547,1548,1550,1552],{"class":290,"line":363},[288,1549,1182],{"class":321},[288,1551,1185],{"class":298},[288,1553,1188],{"class":321},[288,1555,1556,1558,1561,1564,1567],{"class":290,"line":374},[288,1557,759],{"class":438},[288,1559,1560],{"class":321},"(ws[",[288,1562,1563],{"class":298},"\"G3\"",[288,1565,1566],{"class":321},"].value)          ",[288,1568,1569],{"class":795},"# =IF(B3>=$E$2,3,IF(B3>=$D$2,2,1))\n",[10,1571,1572,1573,998],{},"The formula can combine anything Excel can calculate, and the icon rule simply maps 1, 2 and 3 to its three icons. Writing formulas with openpyxl is covered in ",[19,1574,1576],{"href":1575},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fwrite-formula-to-excel-cell-with-openpyxl\u002F","Write a Formula to an Excel Cell with openpyxl",[25,1578,34,1583,34,1586,34,1589,34,1592,34,1598,34,1606,34,1612,34,1616,34,1620,34,1624,34,1629,34,1634,34,1637,34,1644,34,1650,34,1654,34,1658,34,1662,34,1665,34,1668,34,1675,34,1679,34,1682,34,1684,34,1688,34,1691,34,1694,34,1700,34,1704,34,1708,34,1710,34,1714,34,1717,34,1720,34,1725,34,1729,34,1732],{"viewBox":1579,"role":28,"ariaLabelledBy":1580,"xmlns":32,"style":1005},"4 0 752 160",[1581,1582],"is-flow-t","is-flow-d",[36,1584,1585],{"id":1581},"Adding an icon set that stays correct",[40,1587,1588],{"id":1582},"Choose an icon set whose meaning matches the measure, list one lower-bound threshold per icon starting from the lowest, use num thresholds or cell references for targets, adjust gte, reverse or showValue if needed, then reload the saved file and check the rule.",[44,1590],{"x":201,"y":47,"width":1014,"height":1591,"fill":50},"160",[44,1593],{"x":1018,"y":54,"width":1594,"height":1595,"rx":1596,"fill":1024,"stroke":1025,"style":1597},"115.2","128.0","12","stroke-width:2px",[1058,1599,1601],{"transform":1600},"translate(65.6,29.0) scale(1.000)",[1062,1602],{"d":1603,"fill":1065,"stroke":1604,"style":1605},"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 .01 0z","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[65,1607,1611],{"x":1608,"y":1609,"style":1610},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Choose the",[65,1613,1615],{"x":1608,"y":1614,"style":1610},"93.0","set",[65,1617,1619],{"x":1608,"y":1618,"style":1053},"112.5","lights, arrows,",[65,1621,1623],{"x":1608,"y":1622,"style":1053},"130.0","rating",[290,1625],{"x1":1626,"y1":1627,"x2":1628,"y2":1627,"stroke":1025,"style":1597},"140.2","80.0","162.2",[1630,1631],"polygon",{"points":1632,"fill":1633},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[44,1635],{"x":1636,"y":54,"width":1594,"height":1595,"rx":1596,"fill":183,"stroke":184,"style":1597},"171.2",[1058,1638,1640],{"transform":1639},"translate(216.8,37.8) scale(1.000)",[1062,1641],{"d":1642,"fill":1065,"stroke":1643,"style":1605},"M3 5h18l-7 8v6l-4 2v-8z","var(--gold-ink,#7a4e06)",[65,1645,1649],{"x":1646,"y":1647,"style":1648},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Lower",[65,1651,1653],{"x":1646,"y":1652,"style":1648},"101.8","bounds",[65,1655,1657],{"x":1646,"y":1656,"style":1053},"121.2","one per icon",[290,1659],{"x1":1660,"y1":1627,"x2":1661,"y2":1627,"stroke":1025,"style":1597},"291.4","313.4",[1630,1663],{"points":1664,"fill":1633},"317.4,80.0 308.4,85.0 308.4,75.0",[44,1666],{"x":1667,"y":54,"width":1594,"height":1595,"rx":1596,"fill":150,"stroke":151,"style":1597},"322.4",[1058,1669,1671],{"transform":1670},"translate(368.0,37.8) scale(1.000)",[1062,1672],{"d":1673,"fill":1065,"stroke":1674,"style":1605},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1","var(--teal-ink,#0b6157)",[65,1676,1678],{"x":269,"y":1647,"style":1677},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Targets in",[65,1680,1681],{"x":269,"y":1652,"style":1677},"cells",[65,1683,1159],{"x":269,"y":1656,"style":1053},[290,1685],{"x1":1686,"y1":1627,"x2":1687,"y2":1627,"stroke":1025,"style":1597},"442.6","464.6",[1630,1689],{"points":1690,"fill":1633},"468.6,80.0 459.6,85.0 459.6,75.0",[44,1692],{"x":1693,"y":54,"width":1594,"height":1595,"rx":1596,"fill":1024,"stroke":1025,"style":1597},"473.6",[1058,1695,1697],{"transform":1696},"translate(519.2,37.8) scale(1.000)",[1062,1698],{"d":1699,"fill":1065,"stroke":1604,"style":1605},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[65,1701,1703],{"x":1702,"y":1647,"style":1610},"531.2","Fine-tune",[65,1705,1707],{"x":1702,"y":1706,"style":1053},"103.8","gte, reverse,",[65,1709,1290],{"x":1702,"y":1656,"style":1053},[290,1711],{"x1":1712,"y1":1627,"x2":1713,"y2":1627,"stroke":1025,"style":1597},"593.8","615.8",[1630,1715],{"points":1716,"fill":1633},"619.8,80.0 610.8,85.0 610.8,75.0",[44,1718],{"x":1719,"y":54,"width":1594,"height":1595,"rx":1596,"fill":1024,"stroke":1025,"style":1597},"624.8",[1058,1721,1723],{"transform":1722},"translate(670.4,37.8) scale(1.000)",[1062,1724],{"d":1064,"fill":1065,"stroke":1604,"style":1605},[65,1726,1728],{"x":1727,"y":1647,"style":1610},"682.4","Reload and",[65,1730,1731],{"x":1727,"y":1652,"style":1610},"check",[65,1733,1734],{"x":1727,"y":1656,"style":1053},"after saving",[274,1736,1738],{"id":1737},"step-7-check-the-rules-after-saving","Step 7 — Check the rules after saving",[10,1740,1741],{},"openpyxl does not validate icon rules: passing two thresholds to a three-icon set is accepted without complaint and produces a rule Excel may repair or drop. Reload the file and inspect what was written:",[279,1743,1745],{"className":308,"code":1744,"language":310,"meta":284,"style":284},"for cf in load_workbook(\"attainment.xlsx\")[\"Attainment\"].conditional_formatting:\n    for r in cf.rules:\n        icons = int(r.iconSet.iconSet[0])\n        vals = [(c.type, c.val, c.gte) for c in r.iconSet.cfvo]\n        assert icons == len(vals), f\"{cf.sqref}: {icons} icons but {len(vals)} thresholds\"\n        print(cf.sqref, r.iconSet.iconSet, vals)\n",[14,1746,1747,1769,1782,1799,1818,1871],{"__ignoreMap":284},[288,1748,1749,1751,1754,1756,1759,1761,1764,1766],{"class":290,"line":291},[288,1750,421],{"class":317},[288,1752,1753],{"class":321}," cf ",[288,1755,427],{"class":317},[288,1757,1758],{"class":321}," load_workbook(",[288,1760,1185],{"class":298},[288,1762,1763],{"class":321},")[",[288,1765,397],{"class":298},[288,1767,1768],{"class":321},"].conditional_formatting:\n",[288,1770,1771,1774,1777,1779],{"class":290,"line":331},[288,1772,1773],{"class":317},"    for",[288,1775,1776],{"class":321}," r ",[288,1778,427],{"class":317},[288,1780,1781],{"class":321}," cf.rules:\n",[288,1783,1784,1787,1789,1792,1795,1797],{"class":290,"line":344},[288,1785,1786],{"class":321},"        icons ",[288,1788,357],{"class":317},[288,1790,1791],{"class":438}," int",[288,1793,1794],{"class":321},"(r.iconSet.iconSet[",[288,1796,47],{"class":438},[288,1798,415],{"class":321},[288,1800,1801,1804,1806,1809,1811,1813,1815],{"class":290,"line":351},[288,1802,1803],{"class":321},"        vals ",[288,1805,357],{"class":317},[288,1807,1808],{"class":321}," [(c.type, c.val, c.gte) ",[288,1810,421],{"class":317},[288,1812,857],{"class":321},[288,1814,427],{"class":317},[288,1816,1817],{"class":321}," r.iconSet.cfvo]\n",[288,1819,1820,1823,1826,1829,1832,1835,1837,1839,1841,1844,1846,1849,1851,1854,1856,1859,1861,1863,1866,1868],{"class":290,"line":363},[288,1821,1822],{"class":317},"        assert",[288,1824,1825],{"class":321}," icons ",[288,1827,1828],{"class":317},"==",[288,1830,1831],{"class":438}," len",[288,1833,1834],{"class":321},"(vals), ",[288,1836,577],{"class":317},[288,1838,580],{"class":298},[288,1840,584],{"class":583},[288,1842,1843],{"class":321},"cf.sqref",[288,1845,590],{"class":583},[288,1847,1848],{"class":298},": ",[288,1850,584],{"class":583},[288,1852,1853],{"class":321},"icons",[288,1855,590],{"class":583},[288,1857,1858],{"class":298}," icons but ",[288,1860,584],{"class":583},[288,1862,770],{"class":438},[288,1864,1865],{"class":321},"(vals)",[288,1867,590],{"class":583},[288,1869,1870],{"class":298}," thresholds\"\n",[288,1872,1873,1876],{"class":290,"line":374},[288,1874,1875],{"class":438},"        print",[288,1877,1878],{"class":321},"(cf.sqref, r.iconSet.iconSet, vals)\n",[10,1880,1881,1882,998],{},"Listing and removing existing rules is covered in ",[19,1883,1885],{"href":1884},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Flist-and-remove-conditional-formatting-with-openpyxl\u002F","List and Remove Conditional Formatting with openpyxl",[274,1887,1889],{"id":1888},"common-pitfalls","Common pitfalls",[1891,1892,1893,1901,1920,1926,1932],"ul",{},[1894,1895,1896,1900],"li",{},[1897,1898,1899],"strong",{},"Thresholds in the wrong order."," They go from the lowest icon to the highest. Listing them from green down gives every value the top icon.",[1894,1902,1903,1906,1907,1909,1910,1913,1914,1916,1917,998],{},[1897,1904,1905],{},"Percent values as whole numbers."," With ",[14,1908,981],{},", 90% is ",[14,1911,1912],{},"0.9",", because the cell holds 0.9. With ",[14,1915,985],{},", the threshold is ",[14,1918,1919],{},"90",[1894,1921,1922,1925],{},[1897,1923,1924],{},"Icons on numbers stored as text."," Icon sets ignore text, so a column of \"95%\" strings shows no icons at all. Convert the values to numbers before writing them.",[1894,1927,1928,1931],{},[1897,1929,1930],{},"Rules added on every run."," Re-running a script against an existing workbook adds the rule again each time, and the duplicates pile up in Excel's rule manager. Clear the range's rules before adding, or build the workbook from scratch.",[1894,1933,1934,1937,1938,1941],{},[1897,1935,1936],{},"Colour-only meaning."," Some readers cannot tell red from green. Prefer sets whose shapes differ, such as ",[14,1939,1940],{},"3Symbols"," or arrows, or keep the value visible.",[274,1943,1945],{"id":1944},"conclusion","Conclusion",[10,1947,1948,1949,1951,1952,394,1954,1956,1957,1959],{},"Icon sets turn numbers into verdicts that update with the data. Choose a built-in set whose meaning fits the measure, give one lower-bound threshold per icon from the lowest up, use ",[14,1950,981],{}," thresholds — ideally cell references readers can edit — and adjust ",[14,1953,1411],{},[14,1955,1251],{}," and ",[14,1958,1290],{}," through the rule's threshold objects. Compute complex statuses in a helper column, and reload the file to check every rule has as many thresholds as icons.",[274,1961,1963],{"id":1962},"frequently-asked-questions","Frequently asked questions",[10,1965,1966,1969,1970,1973],{},[1897,1967,1968],{},"How do I add traffic lights to Excel cells with Python?","\nCreate IconSetRule(\"3TrafficLights1\", \"num\", ",[288,1971,1972],{},"0, amber_from, green_from",") and add it to the range with ws.conditional_formatting.add. Each value is the lower bound of an icon's band, starting with the lowest icon.",[10,1975,1976,1979],{},[1897,1977,1978],{},"What is the difference between num, percent and percentile?","\nnum compares cell values with fixed numbers. percent places the thresholds between the range's minimum and maximum, and percentile uses the distribution of values. Only num gives the same icon for the same value regardless of the other cells.",[10,1981,1982,1985],{},[1897,1983,1984],{},"Can the thresholds come from cells in the sheet?","\nYes. Pass a cell reference such as \"$E$2\" as a threshold value. Readers can then change the target in the cell and the icons update without editing the rule.",[10,1987,1988,1991],{},[1897,1989,1990],{},"How do I show only the icon without the number?","\nPass showValue=False. The cell keeps its value for formulas and sorting, but only the icon is displayed.",[274,1993,1995],{"id":1994},"related","Related",[1891,1997,1998,2004,2011,2018],{},[1894,1999,2000,2001,2003],{},"Up one level: ",[19,2002,22],{"href":21}," — rules that follow the data.",[1894,2005,2006,2010],{},[19,2007,2009],{"href":2008},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-above-a-threshold-with-openpyxl\u002F","Highlight Cells Above a Threshold with openpyxl"," — fills instead of icons.",[1894,2012,2013,2017],{},[19,2014,2016],{"href":2015},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-an-entire-row-based-on-a-cell-value-with-openpyxl\u002F","Highlight an Entire Row Based on a Cell Value with openpyxl"," — formula rules across rows.",[1894,2019,2020,2024],{},[19,2021,2023],{"href":2022},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard\u002F","Add a KPI Summary Block to an Excel Dashboard"," — putting status icons to work.",[2026,2027,2028],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":284,"searchDepth":331,"depth":331,"links":2030},[2031,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042],{"id":276,"depth":331,"text":277},{"id":619,"depth":331,"text":620},{"id":802,"depth":331,"text":803},{"id":877,"depth":331,"text":878},{"id":1127,"depth":331,"text":1128},{"id":1194,"depth":331,"text":1195},{"id":1419,"depth":331,"text":1420},{"id":1737,"depth":331,"text":1738},{"id":1888,"depth":331,"text":1889},{"id":1944,"depth":331,"text":1945},{"id":1962,"depth":331,"text":1963},{"id":1994,"depth":331,"text":1995},"2026-09-23","Add traffic lights, arrows, flags and rating icons to an Excel report with openpyxl's IconSetRule — choose the icon set, set thresholds as numbers, percents, percentiles or cells the reader can edit, control inclusive boundaries, show icons alone, and drive status icons from a calculated column.","md",[2047,2049,2051,2053],{"q":1968,"a":2048},"Create IconSetRule(\"3TrafficLights1\", \"num\", [0, amber_from, green_from]) and add it to the range with ws.conditional_formatting.add. Each value is the lower bound of an icon's band, starting with the lowest icon.",{"q":1978,"a":2050},"num compares cell values with fixed numbers. percent places the thresholds between the range's minimum and maximum, and percentile uses the distribution of values. Only num gives the same icon for the same value regardless of the other cells.",{"q":1984,"a":2052},"Yes. Pass a cell reference such as \"$E$2\" as a threshold value. Readers can then change the target in the cell and the icons update without editing the rule.",{"q":1990,"a":2054},"Pass showValue=False. The cell keeps its value for formulas and sorting, but only the icon is displayed.","add icon sets to excel cells with openpyxl",null,{"breadcrumb":2058},[2059,2062,2065],{"name":2060,"item":2061},"Home","\u002F",{"name":2063,"item":2064},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2066,"item":21},"Applying Conditional Formatting With openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl",{"title":2069,"description":2070},"Add Icon Sets to Excel Cells with openpyxl (IconSetRule)","Traffic-light, arrow and rating icon sets with openpyxl — IconSetRule names, num vs percent vs percentile thresholds, thresholds read from cells, gte for exclusive boundaries, reverse and showValue, icons from a status column and checking rules after saving.","add-icon-sets-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-icon-sets-with-openpyxl\u002Findex",[2074,2075],"conditional-formatting","openpyxl","how-to","4tBZIK-oYaLC75K8EmtRGwEB0ymdtXef1oK9xvD-9rU",[2079,2082],{"title":997,"path":2080,"stem":2081,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl\u002Findex",{"title":2083,"path":2084,"stem":2085,"children":-1},"Apply a Colour Scale per Row or per Column with openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-a-colour-scale-per-row-or-per-column-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-a-colour-scale-per-row-or-per-column-with-openpyxl\u002Findex",[2087,2089],{"path":2088,"title":2066},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl",{"path":2090,"title":2063},"\u002Fadvanced-data-transformation-and-cleaning",[],[2093,2096,2097,2098,2101,2104],{"path":2094,"title":2095,"kind":2076},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","Apply Conditional Formatting with xlsxwriter",{"path":2080,"title":997,"kind":2076},{"path":2084,"title":2083,"kind":2076},{"path":2099,"title":2100,"kind":2076},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-conditional-formatting-to-an-excel-table-that-grows","Apply Conditional Formatting to an Excel Table That Grows",{"path":2102,"title":2103,"kind":2076},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fcontrol-conditional-formatting-rule-order-and-stop-if-true","Control Conditional Formatting Rule Order and Stop If True",{"path":2105,"title":2016,"kind":2076},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-an-entire-row-based-on-a-cell-value-with-openpyxl",1790972078028]