[{"data":1,"prerenderedAt":1835},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl":1826},{"id":4,"title":5,"body":6,"dateModified":1795,"datePublished":1795,"description":1796,"extension":1797,"faq":1798,"meta":1807,"navigation":243,"path":1818,"seo":1819,"slug":1822,"stem":1823,"type":1824,"__hash__":1825},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl\u002Findex.md","Add Data Bars and Colour Scales with openpyxl",{"type":7,"value":8,"toc":1781},"minimark",[9,13,31,175,180,208,211,215,590,593,597,774,784,793,797,804,892,895,1002,1006,1009,1257,1266,1269,1273,1375,1387,1395,1399,1402,1523,1526,1529,1532,1536,1661,1665,1672,1675,1679,1682,1686,1699,1713,1719,1729,1733,1736,1745,1748,1777],[10,11,12],"p",{},"A data bar or a colour scale answers a question a number cannot: which of these rows is big, and where does this one sit in the range. Because they are conditional formatting rules rather than computed colours, Excel re-evaluates them whenever the values change — so the visual stays correct after a reader filters, sorts or edits the table.",[10,14,15,16,20,21,24,25,30],{},"This guide covers openpyxl's ",[17,18,19],"code",{},"DataBarRule"," and ",[17,22,23],{},"ColorScaleRule",", the endpoint choice that decides whether the visual is informative or flat, and when each is the right pick. It is part of ",[26,27,29],"a",{"href":28},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002F","Applying Conditional Formatting with openpyxl",".",[32,33,41,42,41,46,41,50,41,57,41,67,41,74,41,82,41,86,41,90,41,94,41,99,41,104,41,109,41,114,41,119,41,122,41,126,41,129,41,132,41,137,41,142,41,146,41,152,41,156,41,162,41,166,41,169,41,172],"svg",{"viewBox":34,"role":35,"ariaLabelledBy":36,"xmlns":39,"style":40},"0 0 740 246","img",[37,38],"db-pick-t","db-pick-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:740px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[43,44,45],"title",{"id":37},"Which in-cell visual answers which question",[47,48,49],"desc",{"id":38},"A data bar compares magnitudes down a column and is read like a tiny bar chart. A colour scale shows where each value sits within the spread of the column, which suits a measure with no natural threshold. An icon set collapses the column into three or four states, which is right when the reader only needs a verdict.",[51,52],"rect",{"x":53,"y":53,"width":54,"height":55,"fill":56},"0","740","246","#ffffff",[51,58],{"x":59,"y":60,"width":61,"height":62,"rx":63,"fill":64,"stroke":65,"style":66},"16","34","228","180","14","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[68,69,73],"text",{"x":70,"y":71,"style":72},"130","62","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","data bar",[51,75],{"x":76,"y":77,"width":78,"height":79,"rx":80,"fill":81},"44","78","120","13","2","#0f766e",[51,83],{"x":76,"y":84,"width":85,"height":79,"rx":80,"fill":81},"96","176",[51,87],{"x":76,"y":88,"width":89,"height":79,"rx":80,"fill":81},"114","64",[51,91],{"x":76,"y":92,"width":93,"height":79,"rx":80,"fill":81},"132","98",[68,95,98],{"x":70,"y":96,"style":97},"172","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","\"which rows are big?\"",[68,100,103],{"x":70,"y":101,"style":102},"196","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","magnitude, no chart needed",[51,105],{"x":106,"y":60,"width":61,"height":62,"rx":63,"fill":107,"stroke":108,"style":66},"256","#fdefd8","var(--gold,#b4740a)",[68,110,113],{"x":111,"y":71,"style":112},"370","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","colour scale",[51,115],{"x":116,"y":77,"width":96,"height":59,"rx":117,"fill":118},"284","3","#C6EFCE",[51,120],{"x":116,"y":93,"width":96,"height":59,"rx":117,"fill":121},"#FFEB9C",[51,123],{"x":116,"y":124,"width":96,"height":59,"rx":117,"fill":125},"118","#FFC7CE",[68,127,128],{"x":111,"y":96,"style":97},"\"where in the range?\"",[68,130,131],{"x":111,"y":101,"style":102},"position, no fixed cut-off",[51,133],{"x":134,"y":60,"width":61,"height":62,"rx":63,"fill":135,"stroke":136,"style":66},"496","#ebebfd","var(--brand,#5b5cf0)",[68,138,141],{"x":139,"y":71,"style":140},"610","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","icon set",[143,144],"polygon",{"points":145,"fill":81},"540,90 548,76 556,90",[51,147],{"x":148,"y":149,"width":59,"height":150,"fill":151},"540","100","5","#8a5808",[143,153],{"points":154,"fill":155},"540,120 548,134 556,120","#d81b73",[68,157,161],{"x":158,"y":159,"style":160},"600","90","font-size:11px;fill:var(--text,#172033)","above target",[68,163,165],{"x":158,"y":164,"style":160},"110","on target",[68,167,168],{"x":158,"y":70,"style":160},"below target",[68,170,171],{"x":139,"y":96,"style":97},"\"is this one ok?\"",[68,173,174],{"x":139,"y":101,"style":102},"a verdict, not a value",[176,177,179],"h2",{"id":178},"prerequisites","Prerequisites",[181,182,187],"pre",{"className":183,"code":184,"language":185,"meta":186,"style":186},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[17,188,189],{"__ignoreMap":186},[190,191,194,198,202,205],"span",{"class":192,"line":193},"line",1,[190,195,197],{"class":196},"sMTad","pip",[190,199,201],{"class":200},"srMev"," install",[190,203,204],{"class":200}," openpyxl",[190,206,207],{"class":200}," pandas\n",[10,209,210],{},"The examples build their own workbook, so they run as written.",[176,212,214],{"id":213},"step-1-a-sheet-with-a-spread-worth-showing","Step 1: A sheet with a spread worth showing",[181,216,220],{"className":217,"code":218,"language":219,"meta":186,"style":186},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.DataFrame({\n    \"region\": [\"North\", \"South\", \"East\", \"West\", \"Central\", \"Highlands\"],\n    \"revenue\": [150_250, 274_750, 75_000, 190_400, 88_100, 1_240_000],\n    \"variance\": [0.041, -0.062, -0.128, 0.012, -0.005, 0.083],\n})\n\nwith pd.ExcelWriter(\"visuals.xlsx\", engine=\"openpyxl\") as writer:\n    df.to_excel(writer, sheet_name=\"Regions\", index=False)\n    ws = writer.sheets[\"Regions\"]\n    for cell in ws[\"B\"][1:]:\n        cell.number_format = '#,##0'\n    for cell in ws[\"C\"][1:]:\n        cell.number_format = \"0.0%\"\n    ws.column_dimensions[\"A\"].width = 14\n    ws.column_dimensions[\"B\"].width = 14\n    ws.column_dimensions[\"C\"].width = 12\n    ws.freeze_panes = \"A2\"\n","python",[17,221,222,238,245,257,298,337,382,388,393,424,451,467,494,505,525,535,552,565,579],{"__ignoreMap":186},[190,223,224,228,232,235],{"class":192,"line":193},[190,225,227],{"class":226},"s-kum","import",[190,229,231],{"class":230},"skGVy"," pandas ",[190,233,234],{"class":226},"as",[190,236,237],{"class":230}," pd\n",[190,239,241],{"class":192,"line":240},2,[190,242,244],{"emptyLinePlaceholder":243},true,"\n",[190,246,248,251,254],{"class":192,"line":247},3,[190,249,250],{"class":230},"df ",[190,252,253],{"class":226},"=",[190,255,256],{"class":230}," pd.DataFrame({\n",[190,258,260,263,266,269,272,275,277,280,282,285,287,290,292,295],{"class":192,"line":259},4,[190,261,262],{"class":200},"    \"region\"",[190,264,265],{"class":230},": [",[190,267,268],{"class":200},"\"North\"",[190,270,271],{"class":230},", ",[190,273,274],{"class":200},"\"South\"",[190,276,271],{"class":230},[190,278,279],{"class":200},"\"East\"",[190,281,271],{"class":230},[190,283,284],{"class":200},"\"West\"",[190,286,271],{"class":230},[190,288,289],{"class":200},"\"Central\"",[190,291,271],{"class":230},[190,293,294],{"class":200},"\"Highlands\"",[190,296,297],{"class":230},"],\n",[190,299,301,304,306,310,312,315,317,320,322,325,327,330,332,335],{"class":192,"line":300},5,[190,302,303],{"class":200},"    \"revenue\"",[190,305,265],{"class":230},[190,307,309],{"class":308},"sP0c6","150_250",[190,311,271],{"class":230},[190,313,314],{"class":308},"274_750",[190,316,271],{"class":230},[190,318,319],{"class":308},"75_000",[190,321,271],{"class":230},[190,323,324],{"class":308},"190_400",[190,326,271],{"class":230},[190,328,329],{"class":308},"88_100",[190,331,271],{"class":230},[190,333,334],{"class":308},"1_240_000",[190,336,297],{"class":230},[190,338,340,343,345,348,350,353,356,358,360,363,365,368,370,372,375,377,380],{"class":192,"line":339},6,[190,341,342],{"class":200},"    \"variance\"",[190,344,265],{"class":230},[190,346,347],{"class":308},"0.041",[190,349,271],{"class":230},[190,351,352],{"class":226},"-",[190,354,355],{"class":308},"0.062",[190,357,271],{"class":230},[190,359,352],{"class":226},[190,361,362],{"class":308},"0.128",[190,364,271],{"class":230},[190,366,367],{"class":308},"0.012",[190,369,271],{"class":230},[190,371,352],{"class":226},[190,373,374],{"class":308},"0.005",[190,376,271],{"class":230},[190,378,379],{"class":308},"0.083",[190,381,297],{"class":230},[190,383,385],{"class":192,"line":384},7,[190,386,387],{"class":230},"})\n",[190,389,391],{"class":192,"line":390},8,[190,392,244],{"emptyLinePlaceholder":243},[190,394,396,399,402,405,407,411,413,416,419,421],{"class":192,"line":395},9,[190,397,398],{"class":226},"with",[190,400,401],{"class":230}," pd.ExcelWriter(",[190,403,404],{"class":200},"\"visuals.xlsx\"",[190,406,271],{"class":230},[190,408,410],{"class":409},"sa561","engine",[190,412,253],{"class":226},[190,414,415],{"class":200},"\"openpyxl\"",[190,417,418],{"class":230},") ",[190,420,234],{"class":226},[190,422,423],{"class":230}," writer:\n",[190,425,427,430,433,435,438,440,443,445,448],{"class":192,"line":426},10,[190,428,429],{"class":230},"    df.to_excel(writer, ",[190,431,432],{"class":409},"sheet_name",[190,434,253],{"class":226},[190,436,437],{"class":200},"\"Regions\"",[190,439,271],{"class":230},[190,441,442],{"class":409},"index",[190,444,253],{"class":226},[190,446,447],{"class":308},"False",[190,449,450],{"class":230},")\n",[190,452,454,457,459,462,464],{"class":192,"line":453},11,[190,455,456],{"class":230},"    ws ",[190,458,253],{"class":226},[190,460,461],{"class":230}," writer.sheets[",[190,463,437],{"class":200},[190,465,466],{"class":230},"]\n",[190,468,470,473,476,479,482,485,488,491],{"class":192,"line":469},12,[190,471,472],{"class":226},"    for",[190,474,475],{"class":230}," cell ",[190,477,478],{"class":226},"in",[190,480,481],{"class":230}," ws[",[190,483,484],{"class":200},"\"B\"",[190,486,487],{"class":230},"][",[190,489,490],{"class":308},"1",[190,492,493],{"class":230},":]:\n",[190,495,497,500,502],{"class":192,"line":496},13,[190,498,499],{"class":230},"        cell.number_format ",[190,501,253],{"class":226},[190,503,504],{"class":200}," '#,##0'\n",[190,506,508,510,512,514,516,519,521,523],{"class":192,"line":507},14,[190,509,472],{"class":226},[190,511,475],{"class":230},[190,513,478],{"class":226},[190,515,481],{"class":230},[190,517,518],{"class":200},"\"C\"",[190,520,487],{"class":230},[190,522,490],{"class":308},[190,524,493],{"class":230},[190,526,528,530,532],{"class":192,"line":527},15,[190,529,499],{"class":230},[190,531,253],{"class":226},[190,533,534],{"class":200}," \"0.0%\"\n",[190,536,538,541,544,547,549],{"class":192,"line":537},16,[190,539,540],{"class":230},"    ws.column_dimensions[",[190,542,543],{"class":200},"\"A\"",[190,545,546],{"class":230},"].width ",[190,548,253],{"class":226},[190,550,551],{"class":308}," 14\n",[190,553,555,557,559,561,563],{"class":192,"line":554},17,[190,556,540],{"class":230},[190,558,484],{"class":200},[190,560,546],{"class":230},[190,562,253],{"class":226},[190,564,551],{"class":308},[190,566,568,570,572,574,576],{"class":192,"line":567},18,[190,569,540],{"class":230},[190,571,518],{"class":200},[190,573,546],{"class":230},[190,575,253],{"class":226},[190,577,578],{"class":308}," 12\n",[190,580,582,585,587],{"class":192,"line":581},19,[190,583,584],{"class":230},"    ws.freeze_panes ",[190,586,253],{"class":226},[190,588,589],{"class":200}," \"A2\"\n",[10,591,592],{},"Highlands at 1.24 million is deliberate. It is the outlier that makes the endpoint choice in step 3 visible instead of theoretical.",[176,594,596],{"id":595},"step-2-add-a-data-bar","Step 2: Add a data bar",[181,598,600],{"className":217,"code":599,"language":219,"meta":186,"style":186},"from openpyxl import load_workbook\nfrom openpyxl.formatting.rule import DataBarRule\n\nwb = load_workbook(\"visuals.xlsx\")\nws = wb[\"Regions\"]\nlast = ws.max_row\n\nws.conditional_formatting.add(\n    f\"B2:B{last}\",\n    DataBarRule(start_type=\"num\", start_value=0,\n                end_type=\"max\",\n                color=\"5B5CF0\", showValue=True),\n)\nwb.save(\"visuals.xlsx\")\n",[17,601,602,615,627,631,645,659,669,673,678,702,726,738,761,765],{"__ignoreMap":186},[190,603,604,607,610,612],{"class":192,"line":193},[190,605,606],{"class":226},"from",[190,608,609],{"class":230}," openpyxl ",[190,611,227],{"class":226},[190,613,614],{"class":230}," load_workbook\n",[190,616,617,619,622,624],{"class":192,"line":240},[190,618,606],{"class":226},[190,620,621],{"class":230}," openpyxl.formatting.rule ",[190,623,227],{"class":226},[190,625,626],{"class":230}," DataBarRule\n",[190,628,629],{"class":192,"line":247},[190,630,244],{"emptyLinePlaceholder":243},[190,632,633,636,638,641,643],{"class":192,"line":259},[190,634,635],{"class":230},"wb ",[190,637,253],{"class":226},[190,639,640],{"class":230}," load_workbook(",[190,642,404],{"class":200},[190,644,450],{"class":230},[190,646,647,650,652,655,657],{"class":192,"line":300},[190,648,649],{"class":230},"ws ",[190,651,253],{"class":226},[190,653,654],{"class":230}," wb[",[190,656,437],{"class":200},[190,658,466],{"class":230},[190,660,661,664,666],{"class":192,"line":339},[190,662,663],{"class":230},"last ",[190,665,253],{"class":226},[190,667,668],{"class":230}," ws.max_row\n",[190,670,671],{"class":192,"line":384},[190,672,244],{"emptyLinePlaceholder":243},[190,674,675],{"class":192,"line":390},[190,676,677],{"class":230},"ws.conditional_formatting.add(\n",[190,679,680,683,686,690,693,696,699],{"class":192,"line":395},[190,681,682],{"class":226},"    f",[190,684,685],{"class":200},"\"B2:B",[190,687,689],{"class":688},"sSjpA","{",[190,691,692],{"class":230},"last",[190,694,695],{"class":688},"}",[190,697,698],{"class":200},"\"",[190,700,701],{"class":230},",\n",[190,703,704,707,710,712,715,717,720,722,724],{"class":192,"line":426},[190,705,706],{"class":230},"    DataBarRule(",[190,708,709],{"class":409},"start_type",[190,711,253],{"class":226},[190,713,714],{"class":200},"\"num\"",[190,716,271],{"class":230},[190,718,719],{"class":409},"start_value",[190,721,253],{"class":226},[190,723,53],{"class":308},[190,725,701],{"class":230},[190,727,728,731,733,736],{"class":192,"line":453},[190,729,730],{"class":409},"                end_type",[190,732,253],{"class":226},[190,734,735],{"class":200},"\"max\"",[190,737,701],{"class":230},[190,739,740,743,745,748,750,753,755,758],{"class":192,"line":469},[190,741,742],{"class":409},"                color",[190,744,253],{"class":226},[190,746,747],{"class":200},"\"5B5CF0\"",[190,749,271],{"class":230},[190,751,752],{"class":409},"showValue",[190,754,253],{"class":226},[190,756,757],{"class":308},"True",[190,759,760],{"class":230},"),\n",[190,762,763],{"class":192,"line":496},[190,764,450],{"class":230},[190,766,767,770,772],{"class":192,"line":507},[190,768,769],{"class":230},"wb.save(",[190,771,404],{"class":200},[190,773,450],{"class":230},[10,775,776,777,779,780,783],{},"Anchoring ",[17,778,719],{}," at zero rather than at the minimum is usually right for a magnitude: with ",[17,781,782],{},"start_type=\"min\"",", the smallest value gets a zero-length bar, which reads as \"nothing\" when it may be a perfectly respectable 75,000.",[10,785,786,789,790,792],{},[17,787,788],{},"showValue=True"," keeps the number in the cell alongside the bar. Setting it to ",[17,791,447],{}," gives a bar-only column, which looks clean and removes the actual figure — worth it only when the exact number genuinely does not matter.",[176,794,796],{"id":795},"step-3-choose-endpoints-that-survive-an-outlier","Step 3: Choose endpoints that survive an outlier",[10,798,799,800,803],{},"With ",[17,801,802],{},"end_type=\"max\"",", Highlands takes the full bar and every other region is squashed into the first eighth of the column. Percentile endpoints clip the extremes so the rest of the distribution still spreads out:",[181,805,807],{"className":217,"code":806,"language":219,"meta":186,"style":186},"ws.conditional_formatting.add(\n    f\"B2:B{last}\",\n    DataBarRule(start_type=\"percentile\", start_value=10,\n                end_type=\"percentile\", end_value=90,\n                color=\"5B5CF0\", showValue=True),\n)\n",[17,808,809,813,829,851,870,888],{"__ignoreMap":186},[190,810,811],{"class":192,"line":193},[190,812,677],{"class":230},[190,814,815,817,819,821,823,825,827],{"class":192,"line":240},[190,816,682],{"class":226},[190,818,685],{"class":200},[190,820,689],{"class":688},[190,822,692],{"class":230},[190,824,695],{"class":688},[190,826,698],{"class":200},[190,828,701],{"class":230},[190,830,831,833,835,837,840,842,844,846,849],{"class":192,"line":247},[190,832,706],{"class":230},[190,834,709],{"class":409},[190,836,253],{"class":226},[190,838,839],{"class":200},"\"percentile\"",[190,841,271],{"class":230},[190,843,719],{"class":409},[190,845,253],{"class":226},[190,847,848],{"class":308},"10",[190,850,701],{"class":230},[190,852,853,855,857,859,861,864,866,868],{"class":192,"line":259},[190,854,730],{"class":409},[190,856,253],{"class":226},[190,858,839],{"class":200},[190,860,271],{"class":230},[190,862,863],{"class":409},"end_value",[190,865,253],{"class":226},[190,867,159],{"class":308},[190,869,701],{"class":230},[190,871,872,874,876,878,880,882,884,886],{"class":192,"line":300},[190,873,742],{"class":409},[190,875,253],{"class":226},[190,877,747],{"class":200},[190,879,271],{"class":230},[190,881,752],{"class":409},[190,883,253],{"class":226},[190,885,757],{"class":308},[190,887,760],{"class":230},[190,889,890],{"class":192,"line":339},[190,891,450],{"class":230},[10,893,894],{},"Values beyond the endpoints simply render at full or empty rather than breaking, so nothing is lost — the outlier still shows as the longest bar, and the other five are now comparable to each other. The 10\u002F90 pair is a reasonable default for report data; tighten it to 25\u002F75 when the tails are long.",[32,896,41,901,41,904,41,907,41,910,41,915,41,920,41,926,41,930,41,934,41,938,41,940,41,943,41,947,41,951,41,955,41,958,41,962,41,965,41,969,41,972,41,976,41,978,41,980,41,982,41,985,41,987,41,990,41,992,41,995,41,997,41,999],{"viewBox":897,"role":35,"ariaLabelledBy":898,"xmlns":39,"style":40},"0 0 740 244",[899,900],"db-scale-t","db-scale-d",[43,902,903],{"id":899},"How one outlier flattens a max-scaled data bar",[47,905,906],{"id":900},"Scaled to the maximum, a single value of 1.24 million takes the full width and the five regions between 75,000 and 275,000 all render as short stubs that cannot be told apart. Scaled to the tenth and ninetieth percentiles, those five spread across the column while the outlier still shows as the longest bar.",[51,908],{"x":53,"y":53,"width":54,"height":909,"fill":56},"244",[68,911,802],{"x":912,"y":913,"style":914},"184","32","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[68,916,919],{"x":76,"y":917,"style":918},"60","font-size:11px;fill:var(--muted,#5b6780)","North",[51,921],{"x":78,"y":922,"width":923,"height":924,"rx":80,"fill":925},"50","26","12","#5b5cf0",[68,927,929],{"x":76,"y":928,"style":918},"84","South",[51,931],{"x":78,"y":932,"width":933,"height":924,"rx":80,"fill":925},"74","47",[68,935,937],{"x":76,"y":936,"style":918},"108","East",[51,939],{"x":78,"y":93,"width":79,"height":924,"rx":80,"fill":925},[68,941,942],{"x":76,"y":92,"style":918},"West",[51,944],{"x":78,"y":945,"width":946,"height":924,"rx":80,"fill":925},"122","33",[68,948,950],{"x":76,"y":949,"style":918},"156","Central",[51,952],{"x":78,"y":953,"width":954,"height":924,"rx":80,"fill":925},"146","15",[68,956,957],{"x":76,"y":62,"style":918},"Highlands",[51,959],{"x":78,"y":960,"width":961,"height":924,"rx":80,"fill":925},"170","212",[68,963,964],{"x":912,"y":961,"style":102},"five regions squashed into one eighth of the column",[68,966,968],{"x":967,"y":913,"style":72},"556","percentile 10 to 90",[68,970,919],{"x":971,"y":917,"style":918},"416",[51,973],{"x":974,"y":922,"width":975,"height":924,"rx":80,"fill":81},"492","126",[68,977,929],{"x":971,"y":928,"style":918},[51,979],{"x":974,"y":932,"width":961,"height":924,"rx":80,"fill":81},[68,981,937],{"x":971,"y":936,"style":918},[51,983],{"x":974,"y":93,"width":984,"height":924,"rx":80,"fill":81},"18",[68,986,942],{"x":971,"y":92,"style":918},[51,988],{"x":974,"y":945,"width":989,"height":924,"rx":80,"fill":81},"164",[68,991,950],{"x":971,"y":949,"style":918},[51,993],{"x":974,"y":953,"width":994,"height":924,"rx":80,"fill":81},"38",[68,996,957],{"x":971,"y":62,"style":918},[51,998],{"x":974,"y":960,"width":961,"height":924,"rx":80,"fill":81},[68,1000,1001],{"x":967,"y":961,"style":102},"the middle spreads out; the outlier still reads as largest",[176,1003,1005],{"id":1004},"step-4-colour-scales-for-position","Step 4: Colour scales for position",[10,1007,1008],{},"A colour scale suits a measure with no agreed threshold, where the useful question is where a value sits relative to the others:",[181,1010,1012],{"className":217,"code":1011,"language":219,"meta":186,"style":186},"from openpyxl.formatting.rule import ColorScaleRule\n\n# Three-colour: red low, amber middle, green high\nws.conditional_formatting.add(\n    f\"B2:B{last}\",\n    ColorScaleRule(\n        start_type=\"percentile\", start_value=10, start_color=\"FFC7CE\",\n        mid_type=\"percentile\", mid_value=50, mid_color=\"FFEB9C\",\n        end_type=\"percentile\", end_value=90, end_color=\"C6EFCE\",\n    ),\n)\n\n# Two-colour, anchored on zero — right for a signed measure like variance\nws.conditional_formatting.add(\n    f\"C2:C{last}\",\n    ColorScaleRule(start_type=\"num\", start_value=-0.1, start_color=\"FFC7CE\",\n                   end_type=\"num\", end_value=0.1, end_color=\"C6EFCE\"),\n)\nwb.save(\"visuals.xlsx\")\n",[17,1013,1014,1025,1029,1035,1039,1055,1060,1089,1119,1148,1153,1157,1161,1166,1170,1187,1218,1245,1249],{"__ignoreMap":186},[190,1015,1016,1018,1020,1022],{"class":192,"line":193},[190,1017,606],{"class":226},[190,1019,621],{"class":230},[190,1021,227],{"class":226},[190,1023,1024],{"class":230}," ColorScaleRule\n",[190,1026,1027],{"class":192,"line":240},[190,1028,244],{"emptyLinePlaceholder":243},[190,1030,1031],{"class":192,"line":247},[190,1032,1034],{"class":1033},"s-wDw","# Three-colour: red low, amber middle, green high\n",[190,1036,1037],{"class":192,"line":259},[190,1038,677],{"class":230},[190,1040,1041,1043,1045,1047,1049,1051,1053],{"class":192,"line":300},[190,1042,682],{"class":226},[190,1044,685],{"class":200},[190,1046,689],{"class":688},[190,1048,692],{"class":230},[190,1050,695],{"class":688},[190,1052,698],{"class":200},[190,1054,701],{"class":230},[190,1056,1057],{"class":192,"line":339},[190,1058,1059],{"class":230},"    ColorScaleRule(\n",[190,1061,1062,1065,1067,1069,1071,1073,1075,1077,1079,1082,1084,1087],{"class":192,"line":384},[190,1063,1064],{"class":409},"        start_type",[190,1066,253],{"class":226},[190,1068,839],{"class":200},[190,1070,271],{"class":230},[190,1072,719],{"class":409},[190,1074,253],{"class":226},[190,1076,848],{"class":308},[190,1078,271],{"class":230},[190,1080,1081],{"class":409},"start_color",[190,1083,253],{"class":226},[190,1085,1086],{"class":200},"\"FFC7CE\"",[190,1088,701],{"class":230},[190,1090,1091,1094,1096,1098,1100,1103,1105,1107,1109,1112,1114,1117],{"class":192,"line":390},[190,1092,1093],{"class":409},"        mid_type",[190,1095,253],{"class":226},[190,1097,839],{"class":200},[190,1099,271],{"class":230},[190,1101,1102],{"class":409},"mid_value",[190,1104,253],{"class":226},[190,1106,922],{"class":308},[190,1108,271],{"class":230},[190,1110,1111],{"class":409},"mid_color",[190,1113,253],{"class":226},[190,1115,1116],{"class":200},"\"FFEB9C\"",[190,1118,701],{"class":230},[190,1120,1121,1124,1126,1128,1130,1132,1134,1136,1138,1141,1143,1146],{"class":192,"line":395},[190,1122,1123],{"class":409},"        end_type",[190,1125,253],{"class":226},[190,1127,839],{"class":200},[190,1129,271],{"class":230},[190,1131,863],{"class":409},[190,1133,253],{"class":226},[190,1135,159],{"class":308},[190,1137,271],{"class":230},[190,1139,1140],{"class":409},"end_color",[190,1142,253],{"class":226},[190,1144,1145],{"class":200},"\"C6EFCE\"",[190,1147,701],{"class":230},[190,1149,1150],{"class":192,"line":426},[190,1151,1152],{"class":230},"    ),\n",[190,1154,1155],{"class":192,"line":453},[190,1156,450],{"class":230},[190,1158,1159],{"class":192,"line":469},[190,1160,244],{"emptyLinePlaceholder":243},[190,1162,1163],{"class":192,"line":496},[190,1164,1165],{"class":1033},"# Two-colour, anchored on zero — right for a signed measure like variance\n",[190,1167,1168],{"class":192,"line":507},[190,1169,677],{"class":230},[190,1171,1172,1174,1177,1179,1181,1183,1185],{"class":192,"line":527},[190,1173,682],{"class":226},[190,1175,1176],{"class":200},"\"C2:C",[190,1178,689],{"class":688},[190,1180,692],{"class":230},[190,1182,695],{"class":688},[190,1184,698],{"class":200},[190,1186,701],{"class":230},[190,1188,1189,1192,1194,1196,1198,1200,1202,1205,1208,1210,1212,1214,1216],{"class":192,"line":537},[190,1190,1191],{"class":230},"    ColorScaleRule(",[190,1193,709],{"class":409},[190,1195,253],{"class":226},[190,1197,714],{"class":200},[190,1199,271],{"class":230},[190,1201,719],{"class":409},[190,1203,1204],{"class":226},"=-",[190,1206,1207],{"class":308},"0.1",[190,1209,271],{"class":230},[190,1211,1081],{"class":409},[190,1213,253],{"class":226},[190,1215,1086],{"class":200},[190,1217,701],{"class":230},[190,1219,1220,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243],{"class":192,"line":554},[190,1221,1222],{"class":409},"                   end_type",[190,1224,253],{"class":226},[190,1226,714],{"class":200},[190,1228,271],{"class":230},[190,1230,863],{"class":409},[190,1232,253],{"class":226},[190,1234,1207],{"class":308},[190,1236,271],{"class":230},[190,1238,1140],{"class":409},[190,1240,253],{"class":226},[190,1242,1145],{"class":200},[190,1244,760],{"class":230},[190,1246,1247],{"class":192,"line":567},[190,1248,450],{"class":230},[190,1250,1251,1253,1255],{"class":192,"line":581},[190,1252,769],{"class":230},[190,1254,404],{"class":200},[190,1256,450],{"class":230},[10,1258,1259,1260,1262,1263,1265],{},"The colours pair with the positions in order, so ",[17,1261,1081],{}," belongs to ",[17,1264,709],{},". For a measure where high is bad — days overdue, error counts — put the red at the end rather than trying to reverse the data.",[10,1267,1268],{},"Anchoring the variance scale at fixed values rather than percentiles is deliberate: a percentile scale recolours itself every month as the data moves, so an unchanged −2% can look green in one report and amber in the next. Fixed endpoints make two consecutive reports comparable, which is usually what the reader assumes is happening anyway.",[176,1270,1272],{"id":1271},"step-5-icon-sets-when-only-the-verdict-matters","Step 5: Icon sets when only the verdict matters",[181,1274,1276],{"className":217,"code":1275,"language":219,"meta":186,"style":186},"from openpyxl.formatting.rule import IconSetRule\n\nws.conditional_formatting.add(\n    f\"C2:C{last}\",\n    IconSetRule(\"3Arrows\", \"num\", [-0.02, 0, 0.02],\n                showValue=True, reverse=False),\n)\nwb.save(\"visuals.xlsx\")\n",[17,1277,1278,1289,1293,1297,1313,1343,1363,1367],{"__ignoreMap":186},[190,1279,1280,1282,1284,1286],{"class":192,"line":193},[190,1281,606],{"class":226},[190,1283,621],{"class":230},[190,1285,227],{"class":226},[190,1287,1288],{"class":230}," IconSetRule\n",[190,1290,1291],{"class":192,"line":240},[190,1292,244],{"emptyLinePlaceholder":243},[190,1294,1295],{"class":192,"line":247},[190,1296,677],{"class":230},[190,1298,1299,1301,1303,1305,1307,1309,1311],{"class":192,"line":259},[190,1300,682],{"class":226},[190,1302,1176],{"class":200},[190,1304,689],{"class":688},[190,1306,692],{"class":230},[190,1308,695],{"class":688},[190,1310,698],{"class":200},[190,1312,701],{"class":230},[190,1314,1315,1318,1321,1323,1325,1328,1330,1333,1335,1337,1339,1341],{"class":192,"line":300},[190,1316,1317],{"class":230},"    IconSetRule(",[190,1319,1320],{"class":200},"\"3Arrows\"",[190,1322,271],{"class":230},[190,1324,714],{"class":200},[190,1326,1327],{"class":230},", [",[190,1329,352],{"class":226},[190,1331,1332],{"class":308},"0.02",[190,1334,271],{"class":230},[190,1336,53],{"class":308},[190,1338,271],{"class":230},[190,1340,1332],{"class":308},[190,1342,297],{"class":230},[190,1344,1345,1348,1350,1352,1354,1357,1359,1361],{"class":192,"line":339},[190,1346,1347],{"class":409},"                showValue",[190,1349,253],{"class":226},[190,1351,757],{"class":308},[190,1353,271],{"class":230},[190,1355,1356],{"class":409},"reverse",[190,1358,253],{"class":226},[190,1360,447],{"class":308},[190,1362,760],{"class":230},[190,1364,1365],{"class":192,"line":384},[190,1366,450],{"class":230},[190,1368,1369,1371,1373],{"class":192,"line":390},[190,1370,769],{"class":230},[190,1372,404],{"class":200},[190,1374,450],{"class":230},[10,1376,1377,1378,1382,1383,1386],{},"The three thresholds are the ",[1379,1380,1381],"em",{},"lower bounds"," of each icon's band, so the first value should be at or below the smallest number you expect. ",[17,1384,1385],{},"showValue=False"," gives an icon-only column, which works well as a status marker beside a labelled figure rather than in place of one.",[10,1388,1389,1390,1394],{},"Icon sets are the least portable of the three: LibreOffice and several viewers render them inconsistently, so if the workbook goes outside your organisation, a colour scale or a plain ",[26,1391,1393],{"href":1392},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fopenpyxl-apply-conditional-formatting-to-range\u002F","cell rule"," travels better.",[176,1396,1398],{"id":1397},"why-a-rule-beats-a-computed-colour","Why a rule beats a computed colour",[10,1400,1401],{},"It is tempting to skip all of this and colour the cells directly — work out the percentile in pandas, then write a fill per cell. The output looks identical the moment it is generated, and it stops being identical the moment anyone touches it.",[32,1403,41,1408,41,1411,41,1414,41,1417,41,1421,41,1427,41,1431,41,1436,41,1439,41,1444,41,1446,41,1451,41,1455,41,1457,41,1459,41,1461,41,1463,41,1465,41,1467,41,1470,41,1474,41,1478,41,1481,41,1484,41,1487,41,1490,41,1492,41,1494,41,1496,41,1498,41,1501,41,1504,41,1506,41,1508,41,1510,41,1512,41,1514,41,1517,41,1520],{"viewBox":1404,"role":35,"ariaLabelledBy":1405,"xmlns":39,"style":40},"0 0 740 248",[1406,1407],"db-live-t","db-live-d",[43,1409,1410],{"id":1406},"What happens to each approach when a reader sorts the table",[47,1412,1413],{"id":1407},"A fill written into each cell belongs to the cell, so sorting the rows carries the colours along with their old positions and the shading no longer matches the values. A conditional formatting rule belongs to the range, so Excel re-evaluates it after the sort and the shading follows the numbers.",[51,1415],{"x":53,"y":53,"width":54,"height":1416,"fill":56},"248",[68,1418,1420],{"x":912,"y":1419,"style":914},"30","a fill written per cell",[68,1422,1426],{"x":1423,"y":1424,"style":1425},"52","56","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","before",[51,1428],{"x":1429,"y":1430,"width":78,"height":1429,"rx":117,"fill":118},"24","66",[68,1432,1435],{"x":928,"y":1433,"style":1434},"83","font-size:11px;fill:#006100;text-anchor:middle","1,240,000",[51,1437],{"x":1429,"y":1438,"width":78,"height":1429,"rx":117,"fill":121},"94",[68,1440,1443],{"x":928,"y":1441,"style":1442},"111","font-size:11px;fill:#7a4e06;text-anchor:middle","274,750",[51,1445],{"x":1429,"y":945,"width":78,"height":1429,"rx":117,"fill":125},[68,1447,1450],{"x":928,"y":1448,"style":1449},"139","font-size:11px;fill:#9C0006;text-anchor:middle","75,000",[68,1452,1454],{"x":1453,"y":1424,"style":1425},"236","after sorting",[51,1456],{"x":85,"y":1430,"width":78,"height":1429,"rx":117,"fill":125},[68,1458,1450],{"x":1453,"y":1433,"style":1449},[51,1460],{"x":85,"y":1438,"width":78,"height":1429,"rx":117,"fill":121},[68,1462,1443],{"x":1453,"y":1441,"style":1442},[51,1464],{"x":85,"y":945,"width":78,"height":1429,"rx":117,"fill":118},[68,1466,1435],{"x":1453,"y":1448,"style":1434},[68,1468,1469],{"x":912,"y":85,"style":97},"the colours travelled with the rows",[68,1471,1473],{"x":912,"y":1472,"style":102},"198","the largest value is now red and the smallest",[68,1475,1477],{"x":912,"y":1476,"style":102},"216","is green — and nothing reports the error",[68,1479,1480],{"x":967,"y":1419,"style":72},"a rule on the range",[68,1482,1426],{"x":1483,"y":1424,"style":1425},"424",[51,1485],{"x":1486,"y":1430,"width":78,"height":1429,"rx":117,"fill":118},"396",[68,1488,1435],{"x":1489,"y":1433,"style":1434},"456",[51,1491],{"x":1486,"y":1438,"width":78,"height":1429,"rx":117,"fill":121},[68,1493,1443],{"x":1489,"y":1441,"style":1442},[51,1495],{"x":1486,"y":945,"width":78,"height":1429,"rx":117,"fill":125},[68,1497,1450],{"x":1489,"y":1448,"style":1449},[68,1499,1454],{"x":1500,"y":1424,"style":1425},"608",[51,1502],{"x":1503,"y":1430,"width":78,"height":1429,"rx":117,"fill":118},"548",[68,1505,1435],{"x":1500,"y":1433,"style":1434},[51,1507],{"x":1503,"y":1438,"width":78,"height":1429,"rx":117,"fill":121},[68,1509,1443],{"x":1500,"y":1441,"style":1442},[51,1511],{"x":1503,"y":945,"width":78,"height":1429,"rx":117,"fill":125},[68,1513,1450],{"x":1500,"y":1448,"style":1449},[68,1515,1516],{"x":967,"y":85,"style":97},"Excel re-evaluated after the sort",[68,1518,1519],{"x":967,"y":1472,"style":102},"the shading still describes the values,",[68,1521,1522],{"x":967,"y":1476,"style":102},"and keeps doing so as they are edited",[10,1524,1525],{},"Sorting is the obvious case, but the same applies to every ordinary thing a reader does: filtering, pasting in an updated figure, inserting a row. A written fill is a statement about a cell; a rule is a statement about the data, and only the second survives being used.",[10,1527,1528],{},"There are two situations where writing the colour directly is still right. The first is a value that is not on a scale at all — a flag, a status word, an exception marker that came out of a validation step and has no numeric meaning for Excel to re-evaluate. The second is a workbook destined for conversion rather than reading: a sheet exported straight to PDF is a snapshot, and a rule and a fill produce the same picture.",[10,1530,1531],{},"Everywhere else, the rule is the cheaper choice as well as the more correct one. It is a handful of bytes in the file regardless of the range, where a per-cell fill is a style record per cell — the difference between a workbook that opens instantly and one that takes a moment to render every time it is scrolled.",[176,1533,1535],{"id":1534},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[1537,1538,1539,1555],"table",{},[1540,1541,1542],"thead",{},[1543,1544,1545,1549,1552],"tr",{},[1546,1547,1548],"th",{},"Symptom",[1546,1550,1551],{},"Cause",[1546,1553,1554],{},"Fix",[1556,1557,1558,1572,1587,1602,1617,1628,1639,1650],"tbody",{},[1543,1559,1560,1564,1569],{},[1561,1562,1563],"td",{},"Every bar looks the same length",[1561,1565,1566,1567],{},"One outlier with ",[17,1568,802],{},[1561,1570,1571],{},"Percentile endpoints, 10\u002F90",[1543,1573,1574,1577,1581],{},[1561,1575,1576],{},"The shortest value shows no bar",[1561,1578,1579],{},[17,1580,782],{},[1561,1582,1583,1584,1586],{},"Anchor ",[17,1585,719],{}," at 0",[1543,1588,1589,1592,1595],{},[1561,1590,1591],{},"Colours are inverted",[1561,1593,1594],{},"Colour and position order mismatched",[1561,1596,1597,1599,1600],{},[17,1598,1081],{}," pairs with ",[17,1601,709],{},[1543,1603,1604,1607,1610],{},[1561,1605,1606],{},"Colours shift between months",[1561,1608,1609],{},"Percentile endpoints on a comparison",[1561,1611,1612,1613,1616],{},"Fixed ",[17,1614,1615],{},"num"," endpoints",[1543,1618,1619,1622,1625],{},[1561,1620,1621],{},"Negative bars point the wrong way",[1561,1623,1624],{},"Old data bar specification",[1561,1626,1627],{},"Use a three-colour scale instead",[1543,1629,1630,1633,1636],{},[1561,1631,1632],{},"Rule applies to the header",[1561,1634,1635],{},"Range started at row 1",[1561,1637,1638],{},"Start at row 2",[1543,1640,1641,1644,1647],{},[1561,1642,1643],{},"Nothing shows for a text column",[1561,1645,1646],{},"Rules need numbers",[1561,1648,1649],{},"Convert the column first",[1543,1651,1652,1655,1658],{},[1561,1653,1654],{},"Rule lost after re-saving with pandas",[1561,1656,1657],{},"The sheet was rewritten",[1561,1659,1660],{},"Add rules after the data is written",[176,1662,1664],{"id":1663},"performance-and-scale-notes","Performance and scale notes",[10,1666,1667,1668,1671],{},"One rule over a large range is cheap to write and cheap to store. Thousands of single-cell rules are neither — if a loop is calling ",[17,1669,1670],{},"conditional_formatting.add"," per row, replace it with one call over the whole range.",[10,1673,1674],{},"Excel re-evaluates colour scales and data bars on every recalculation, and over hundreds of thousands of rows that is noticeable when scrolling. On a large detail sheet, put the visuals on the summary and leave the detail plain; the reader is scanning the summary anyway. Where the range is large and openpyxl's memory footprint matters, note that conditional formatting cannot be added in write-only mode — build the sheet normally, or apply the rules in a second pass.",[176,1676,1678],{"id":1677},"conclusion","Conclusion",[10,1680,1681],{},"Data bars answer \"which is biggest\", colour scales answer \"where does this sit\", and icon sets answer \"is this one all right\". Pick endpoints deliberately: percentiles when one outlier would flatten the column, fixed numbers when two consecutive reports need to be comparable. Add one rule per range rather than per cell, keep the header out of the range, and remember that the rule lives in the file — so it stays right when the reader edits, sorts or filters underneath it.",[176,1683,1685],{"id":1684},"frequently-asked-questions","Frequently asked questions",[10,1687,1688,1692,20,1695,1698],{},[1689,1690,1691],"strong",{},"What is the difference between min\u002Fmax and percentile endpoints?",[17,1693,1694],{},"min",[17,1696,1697],{},"max"," scale the visual to the actual smallest and largest values, so one outlier flattens everything else. Percentile endpoints — typically 10 and 90 — clip the extremes so the middle of the distribution still spreads across the range.",[10,1700,1701,1704,1706,1707,1709,1710,1712],{},[1689,1702,1703],{},"Why are my colours the wrong way round?",[17,1705,23],{}," takes its colours in the same order as its positions, so ",[17,1708,1081],{}," goes with ",[17,1711,709],{},". For a \"high is bad\" measure, put the red at the max end rather than reversing the values.",[10,1714,1715,1718],{},[1689,1716,1717],{},"Do data bars handle negative numbers?","\nThe rule openpyxl writes uses the older data bar specification, which draws every bar from the left edge. For a column with negatives, a three-colour scale reads correctly where a bar does not.",[10,1720,1721,1724,1725,1728],{},[1689,1722,1723],{},"Can I use these on a sheet pandas wrote?","\nYes. Write the data with pandas, reopen with openpyxl (or reach the sheet through ",[17,1726,1727],{},"writer.sheets","), then add the rule to the range. The rule is stored in the file and applies whenever it is opened.",[176,1730,1732],{"id":1731},"related","Related",[10,1734,1735],{},"Up to the parent guide:",[1737,1738,1739],"ul",{},[1740,1741,1742,1744],"li",{},[26,1743,29],{"href":28}," — the rule families and how Excel evaluates them.",[10,1746,1747],{},"Related guides:",[1737,1749,1750,1756,1763,1770],{},[1740,1751,1752,1755],{},[26,1753,1754],{"href":1392},"openpyxl: Apply Conditional Formatting to a Range"," — range strings, anchors and multi-block ranges.",[1740,1757,1758,1762],{},[26,1759,1761],{"href":1760},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002F","Apply Conditional Formatting with xlsxwriter"," — the same visuals on the write-only engine.",[1740,1764,1765,1769],{},[26,1766,1768],{"href":1767},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fhighlight-invalid-cells-in-excel-with-python\u002F","Highlight Invalid Cells in Excel with Python"," — flagging failures rather than showing a distribution.",[1740,1771,1772,1776],{},[26,1773,1775],{"href":1774},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-excel-cells-as-currency-with-python\u002F","Format Excel Cells as Currency with Python"," — the number formats these visuals sit on top of.",[1778,1779,1780],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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}",{"title":186,"searchDepth":240,"depth":240,"links":1782},[1783,1784,1785,1786,1787,1788,1789,1790,1791,1792,1793,1794],{"id":178,"depth":240,"text":179},{"id":213,"depth":240,"text":214},{"id":595,"depth":240,"text":596},{"id":795,"depth":240,"text":796},{"id":1004,"depth":240,"text":1005},{"id":1271,"depth":240,"text":1272},{"id":1397,"depth":240,"text":1398},{"id":1534,"depth":240,"text":1535},{"id":1663,"depth":240,"text":1664},{"id":1677,"depth":240,"text":1678},{"id":1684,"depth":240,"text":1685},{"id":1731,"depth":240,"text":1732},"2026-08-11","In-cell visuals that survive edits: DataBarRule and ColorScaleRule, percentile versus fixed endpoints, negative values, icon sets, and how to pick between a bar, a scale and a plain threshold.","md",[1799,1801,1803,1805],{"q":1691,"a":1800},"min and max scale the visual to the actual smallest and largest values, so one outlier flattens everything else. Percentile endpoints — typically 10 and 90 — clip the extremes so the middle of the distribution still spreads across the range.",{"q":1703,"a":1802},"ColorScaleRule takes its colours in the same order as its positions, so start_color goes with start_type. For a \"high is bad\" measure, put the red at the max end rather than reversing the values.",{"q":1717,"a":1804},"The rule openpyxl writes uses the older data bar specification, which draws every bar from the left edge. For a column with negatives, a three-colour scale reads correctly where a bar does not.",{"q":1723,"a":1806},"Yes. Write the data with pandas, reopen with openpyxl (or reach the sheet through writer.sheets), then add the rule to the range. The rule is stored in the file and applies whenever it is opened.",{"breadcrumb":1808},[1809,1812,1815,1816],{"name":1810,"item":1811},"Home","\u002F",{"name":1813,"item":1814},"Advanced Data Transformation and Cleaning","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":29,"item":28},{"name":5,"item":1817},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl\u002F","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fadd-data-bars-and-colour-scales-with-openpyxl",{"title":1820,"description":1821},"Data Bars and Colour Scales in openpyxl","Add data bars, two- and three-colour scales and icon sets to Excel from Python with openpyxl — rule endpoints, percentile scaling, negative handling and when each visual is the right one.","add-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","how-to","Svx-We0ReY_QjtEPx2S9yeKgZiMiml4Zqaq_iz2vdug",[1827,1831],{"title":1828,"path":1829,"stem":1830,"children":-1},"Applying Conditional Formatting With openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Findex",{"title":1832,"path":1833,"stem":1834,"children":-1},"Highlight Cells Above a Threshold with openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-above-a-threshold-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-above-a-threshold-with-openpyxl\u002Findex",1786800028610]