[{"data":1,"prerenderedAt":2032},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour":2000,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour":2008,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour":2013,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour":2014},{"id":4,"title":5,"body":6,"dateModified":1966,"datePublished":1966,"description":1967,"extension":1968,"faq":1969,"keyword":1978,"kind":1979,"meta":1980,"navigation":106,"path":1989,"seo":1990,"slug":1993,"stem":1994,"topics":1995,"type":1998,"__hash__":1999},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour\u002Findex.md","Show Negative Bars in a Different Colour",{"type":7,"value":8,"toc":1954},"minimark",[9,24,29,58,61,65,596,622,823,827,1104,1111,1115,1384,1401,1496,1568,1572,1575,1579,1799,1814,1817,1821,1865,1869,1884,1888,1896,1902,1911,1917,1921,1950],[10,11,12,13,17,18,23],"p",{},"A chart of variances, monthly profit or year-on-year change mixes positive and negative values, and the reader's first question is which bars are which. Colouring the negative bars red and the positive ones green answers it before they read a single axis label. Excel has an option for this — ",[14,15,16],"em",{},"Invert if negative"," — but it has a history: the original version simply turns negative bars white, and choosing the colour of the inverted bars was added later through an extension that not every library or application writes. From Python there are three reliable ways to get two-colour bars, each with a different trade-off. This page builds all three for the same eight months of budget variance, shows what each writes into the chart XML, and explains which to choose. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl xlsxwriter\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," openpyxl",[40,56,57],{"class":50}," xlsxwriter\n",[10,59,60],{},"The data is eight months of variance against budget, in thousands of pounds: five months over budget, three under. The first two methods use openpyxl; the third uses XlsxWriter.",[25,62,64],{"id":63},"method-1-two-helper-series-that-follow-the-data","Method 1 — Two helper series that follow the data",[30,66,70],{"className":67,"code":68,"language":69,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\nfrom openpyxl.chart import BarChart, Reference\n\nvariance = [12, -8, 5, -15, 20, 7, -3, 9]\nwb = Workbook()\nws = wb.active\nws.title = \"Variance\"\nws.append([\"Month\", \"Variance\", \"Above budget\", \"Below budget\"])\nfor i, v in enumerate(variance, start=2):\n    ws.append([f\"2026-{i - 1:02d}\", v, f\"=IF(B{i}>=0,B{i},NA())\", f\"=IF(B{i}\u003C0,B{i},NA())\"])\n\nchart = BarChart()\nchart.type = \"col\"\nchart.grouping = \"clustered\"\nchart.overlap = 100                                   # both series in the same slot\nchart.add_data(Reference(ws, min_col=3, max_col=4, min_row=1, max_row=len(variance) + 1), titles_from_data=True)\nchart.set_categories(Reference(ws, min_col=1, min_row=2, max_row=len(variance) + 1))\nabove, below = chart.series\nabove.graphicalProperties.solidFill = \"2E7D32\"\nbelow.graphicalProperties.solidFill = \"C62828\"\nchart.title = \"Variance to budget (£k)\"\nchart.x_axis.delete = chart.y_axis.delete = False\nws.add_chart(chart, \"F2\")\nwb.save(\"variance_split.xlsx\")\nprint(\"saved\")\n","python",[37,71,72,88,101,108,170,181,192,203,230,260,345,350,361,372,383,398,463,500,511,522,533,544,560,571,582],{"__ignoreMap":35},[40,73,74,78,82,85],{"class":42,"line":43},[40,75,77],{"class":76},"s-kum","from",[40,79,81],{"class":80},"skGVy"," openpyxl ",[40,83,84],{"class":76},"import",[40,86,87],{"class":80}," Workbook\n",[40,89,91,93,96,98],{"class":42,"line":90},2,[40,92,77],{"class":76},[40,94,95],{"class":80}," openpyxl.chart ",[40,97,84],{"class":76},[40,99,100],{"class":80}," BarChart, Reference\n",[40,102,104],{"class":42,"line":103},3,[40,105,107],{"emptyLinePlaceholder":106},true,"\n",[40,109,111,114,117,120,124,127,130,133,135,138,140,142,145,147,150,152,155,157,159,162,164,167],{"class":42,"line":110},4,[40,112,113],{"class":80},"variance ",[40,115,116],{"class":76},"=",[40,118,119],{"class":80}," [",[40,121,123],{"class":122},"sP0c6","12",[40,125,126],{"class":80},", ",[40,128,129],{"class":76},"-",[40,131,132],{"class":122},"8",[40,134,126],{"class":80},[40,136,137],{"class":122},"5",[40,139,126],{"class":80},[40,141,129],{"class":76},[40,143,144],{"class":122},"15",[40,146,126],{"class":80},[40,148,149],{"class":122},"20",[40,151,126],{"class":80},[40,153,154],{"class":122},"7",[40,156,126],{"class":80},[40,158,129],{"class":76},[40,160,161],{"class":122},"3",[40,163,126],{"class":80},[40,165,166],{"class":122},"9",[40,168,169],{"class":80},"]\n",[40,171,173,176,178],{"class":42,"line":172},5,[40,174,175],{"class":80},"wb ",[40,177,116],{"class":76},[40,179,180],{"class":80}," Workbook()\n",[40,182,184,187,189],{"class":42,"line":183},6,[40,185,186],{"class":80},"ws ",[40,188,116],{"class":76},[40,190,191],{"class":80}," wb.active\n",[40,193,195,198,200],{"class":42,"line":194},7,[40,196,197],{"class":80},"ws.title ",[40,199,116],{"class":76},[40,201,202],{"class":50}," \"Variance\"\n",[40,204,206,209,212,214,217,219,222,224,227],{"class":42,"line":205},8,[40,207,208],{"class":80},"ws.append([",[40,210,211],{"class":50},"\"Month\"",[40,213,126],{"class":80},[40,215,216],{"class":50},"\"Variance\"",[40,218,126],{"class":80},[40,220,221],{"class":50},"\"Above budget\"",[40,223,126],{"class":80},[40,225,226],{"class":50},"\"Below budget\"",[40,228,229],{"class":80},"])\n",[40,231,233,236,239,242,245,248,252,254,257],{"class":42,"line":232},9,[40,234,235],{"class":76},"for",[40,237,238],{"class":80}," i, v ",[40,240,241],{"class":76},"in",[40,243,244],{"class":122}," enumerate",[40,246,247],{"class":80},"(variance, ",[40,249,251],{"class":250},"sa561","start",[40,253,116],{"class":76},[40,255,256],{"class":122},"2",[40,258,259],{"class":80},"):\n",[40,261,263,266,269,272,276,279,281,284,287,290,293,296,298,301,303,306,308,311,313,315,317,320,322,324,326,328,330,332,335,337,339,341,343],{"class":42,"line":262},10,[40,264,265],{"class":80},"    ws.append([",[40,267,268],{"class":76},"f",[40,270,271],{"class":50},"\"2026-",[40,273,275],{"class":274},"sSjpA","{",[40,277,278],{"class":80},"i ",[40,280,129],{"class":76},[40,282,283],{"class":122}," 1",[40,285,286],{"class":76},":02d",[40,288,289],{"class":274},"}",[40,291,292],{"class":50},"\"",[40,294,295],{"class":80},", v, ",[40,297,268],{"class":76},[40,299,300],{"class":50},"\"=IF(B",[40,302,275],{"class":274},[40,304,305],{"class":80},"i",[40,307,289],{"class":274},[40,309,310],{"class":50},">=0,B",[40,312,275],{"class":274},[40,314,305],{"class":80},[40,316,289],{"class":274},[40,318,319],{"class":50},",NA())\"",[40,321,126],{"class":80},[40,323,268],{"class":76},[40,325,300],{"class":50},[40,327,275],{"class":274},[40,329,305],{"class":80},[40,331,289],{"class":274},[40,333,334],{"class":50},"\u003C0,B",[40,336,275],{"class":274},[40,338,305],{"class":80},[40,340,289],{"class":274},[40,342,319],{"class":50},[40,344,229],{"class":80},[40,346,348],{"class":42,"line":347},11,[40,349,107],{"emptyLinePlaceholder":106},[40,351,353,356,358],{"class":42,"line":352},12,[40,354,355],{"class":80},"chart ",[40,357,116],{"class":76},[40,359,360],{"class":80}," BarChart()\n",[40,362,364,367,369],{"class":42,"line":363},13,[40,365,366],{"class":80},"chart.type ",[40,368,116],{"class":76},[40,370,371],{"class":50}," \"col\"\n",[40,373,375,378,380],{"class":42,"line":374},14,[40,376,377],{"class":80},"chart.grouping ",[40,379,116],{"class":76},[40,381,382],{"class":50}," \"clustered\"\n",[40,384,386,389,391,394],{"class":42,"line":385},15,[40,387,388],{"class":80},"chart.overlap ",[40,390,116],{"class":76},[40,392,393],{"class":122}," 100",[40,395,397],{"class":396},"s-wDw","                                   # both series in the same slot\n",[40,399,401,404,407,409,411,413,416,418,421,423,426,428,431,433,436,438,441,444,447,449,452,455,457,460],{"class":42,"line":400},16,[40,402,403],{"class":80},"chart.add_data(Reference(ws, ",[40,405,406],{"class":250},"min_col",[40,408,116],{"class":76},[40,410,161],{"class":122},[40,412,126],{"class":80},[40,414,415],{"class":250},"max_col",[40,417,116],{"class":76},[40,419,420],{"class":122},"4",[40,422,126],{"class":80},[40,424,425],{"class":250},"min_row",[40,427,116],{"class":76},[40,429,430],{"class":122},"1",[40,432,126],{"class":80},[40,434,435],{"class":250},"max_row",[40,437,116],{"class":76},[40,439,440],{"class":122},"len",[40,442,443],{"class":80},"(variance) ",[40,445,446],{"class":76},"+",[40,448,283],{"class":122},[40,450,451],{"class":80},"), ",[40,453,454],{"class":250},"titles_from_data",[40,456,116],{"class":76},[40,458,459],{"class":122},"True",[40,461,462],{"class":80},")\n",[40,464,466,469,471,473,475,477,479,481,483,485,487,489,491,493,495,497],{"class":42,"line":465},17,[40,467,468],{"class":80},"chart.set_categories(Reference(ws, ",[40,470,406],{"class":250},[40,472,116],{"class":76},[40,474,430],{"class":122},[40,476,126],{"class":80},[40,478,425],{"class":250},[40,480,116],{"class":76},[40,482,256],{"class":122},[40,484,126],{"class":80},[40,486,435],{"class":250},[40,488,116],{"class":76},[40,490,440],{"class":122},[40,492,443],{"class":80},[40,494,446],{"class":76},[40,496,283],{"class":122},[40,498,499],{"class":80},"))\n",[40,501,503,506,508],{"class":42,"line":502},18,[40,504,505],{"class":80},"above, below ",[40,507,116],{"class":76},[40,509,510],{"class":80}," chart.series\n",[40,512,514,517,519],{"class":42,"line":513},19,[40,515,516],{"class":80},"above.graphicalProperties.solidFill ",[40,518,116],{"class":76},[40,520,521],{"class":50}," \"2E7D32\"\n",[40,523,525,528,530],{"class":42,"line":524},20,[40,526,527],{"class":80},"below.graphicalProperties.solidFill ",[40,529,116],{"class":76},[40,531,532],{"class":50}," \"C62828\"\n",[40,534,536,539,541],{"class":42,"line":535},21,[40,537,538],{"class":80},"chart.title ",[40,540,116],{"class":76},[40,542,543],{"class":50}," \"Variance to budget (£k)\"\n",[40,545,547,550,552,555,557],{"class":42,"line":546},22,[40,548,549],{"class":80},"chart.x_axis.delete ",[40,551,116],{"class":76},[40,553,554],{"class":80}," chart.y_axis.delete ",[40,556,116],{"class":76},[40,558,559],{"class":122}," False\n",[40,561,563,566,569],{"class":42,"line":562},23,[40,564,565],{"class":80},"ws.add_chart(chart, ",[40,567,568],{"class":50},"\"F2\"",[40,570,462],{"class":80},[40,572,574,577,580],{"class":42,"line":573},24,[40,575,576],{"class":80},"wb.save(",[40,578,579],{"class":50},"\"variance_split.xlsx\"",[40,581,462],{"class":80},[40,583,585,588,591,594],{"class":42,"line":584},25,[40,586,587],{"class":122},"print",[40,589,590],{"class":80},"(",[40,592,593],{"class":50},"\"saved\"",[40,595,462],{"class":80},[10,597,598,599,602,603,606,607,609,610,613,614,617,618,621],{},"The most portable method avoids the invert option altogether. Two helper columns split the variance: one holds the value when it is zero or positive, the other when it is negative, and each leaves ",[37,600,601],{},"#N\u002FA"," in the months that belong to the other. The chart plots the two helper columns as two series, each with its own fixed colour, and ",[37,604,605],{},"overlap = 100"," puts them in the same slot on the axis, so each month shows one bar in the right colour. ",[37,608,601],{}," is used rather than zero because charts skip it entirely, while a zero would still create an empty bar position and, with data labels on, a misleading ",[14,611,612],{},"0"," label. Because the helper columns are formulas, a corrected variance switches colour on its own the next time the workbook calculates. The two series also give the chart a legend that explains the colours — ",[14,615,616],{},"Above budget"," and ",[14,619,620],{},"Below budget"," — for free.",[623,624,632,633,632,637,632,641,632,648,632,657,632,661,632,668,632,672,632,676,632,680,632,684,632,688,632,692,632,696,632,700,632,705,632,709,632,711,632,714,632,716,632,718,632,720,632,722,632,725,632,728,632,730,632,734,632,736,632,738,632,742,632,745,632,747,632,749,632,752,632,755,632,757,632,760,632,762,632,765,632,767,632,769,632,773,632,776,632,779,632,782,632,784,632,787,632,789,632,791,632,793,632,795,632,797,632,799,632,802,632,805,632,807,632,810,632,812,632,815,632,817,632,819,632,821],"svg",{"viewBox":625,"role":626,"ariaLabelledBy":627,"xmlns":630,"style":631},"158 0 443 196","img",[628,629],"neg-split-t","neg-split-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:443px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[634,635,636],"title",{"id":628},"The helper columns",[638,639,640],"desc",{"id":629},"Each month's variance appears in the Above budget column when it is zero or positive and in the Below budget column when it is negative; the other column holds N\u002FA, which the chart skips.",[642,643],"rect",{"x":644,"y":612,"width":645,"height":646,"fill":647},"158","443","196","#ffffff",[642,649],{"x":650,"y":651,"width":652,"height":653,"rx":612,"fill":654,"stroke":655,"style":656},"174.9","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[642,658],{"x":659,"y":651,"width":660,"height":653,"rx":612,"fill":654,"stroke":655,"style":656},"206.9","79.5",[662,663,667],"text",{"x":664,"y":665,"style":666},"246.6","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[642,669],{"x":670,"y":651,"width":671,"height":653,"rx":612,"fill":654,"stroke":655,"style":656},"286.3","79.4",[662,673,675],{"x":674,"y":665,"style":666},"326.0","B",[642,677],{"x":678,"y":651,"width":679,"height":653,"rx":612,"fill":654,"stroke":655,"style":656},"365.7","110.9",[662,681,683],{"x":682,"y":665,"style":666},"421.2","C",[642,685],{"x":686,"y":651,"width":687,"height":653,"rx":612,"fill":654,"stroke":655,"style":656},"476.6","108.5",[662,689,691],{"x":690,"y":665,"style":666},"530.9","D",[642,693],{"x":650,"y":694,"width":652,"height":695,"rx":612,"fill":654,"stroke":655,"style":656},"40.0","28.0",[662,697,430],{"x":698,"y":699,"style":666},"190.9","59.0",[642,701],{"x":659,"y":694,"width":660,"height":695,"rx":612,"fill":702,"stroke":703,"style":704},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[662,706,708],{"x":664,"y":699,"style":707},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Month",[642,710],{"x":670,"y":694,"width":671,"height":695,"rx":612,"fill":702,"stroke":703,"style":704},[662,712,713],{"x":674,"y":699,"style":707},"Variance",[642,715],{"x":678,"y":694,"width":679,"height":695,"rx":612,"fill":702,"stroke":703,"style":704},[662,717,616],{"x":682,"y":699,"style":707},[642,719],{"x":686,"y":694,"width":687,"height":695,"rx":612,"fill":702,"stroke":703,"style":704},[662,721,620],{"x":690,"y":699,"style":707},[642,723],{"x":650,"y":724,"width":652,"height":695,"rx":612,"fill":654,"stroke":655,"style":656},"68.0",[662,726,256],{"x":698,"y":727,"style":666},"87.0",[642,729],{"x":659,"y":724,"width":660,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,731,733],{"x":664,"y":727,"style":732},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","2026-01",[642,735],{"x":670,"y":724,"width":671,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,737,123],{"x":674,"y":727,"style":732},[642,739],{"x":678,"y":724,"width":679,"height":695,"rx":612,"fill":740,"stroke":741,"style":704},"#d9f4f1","var(--teal,#0f9488)",[662,743,123],{"x":682,"y":727,"style":744},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[642,746],{"x":686,"y":724,"width":687,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,748,601],{"x":690,"y":727,"style":732},[642,750],{"x":650,"y":751,"width":652,"height":695,"rx":612,"fill":654,"stroke":655,"style":656},"96.0",[662,753,161],{"x":698,"y":754,"style":666},"115.0",[642,756],{"x":659,"y":751,"width":660,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,758,759],{"x":664,"y":754,"style":732},"2026-02",[642,761],{"x":670,"y":751,"width":671,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,763,764],{"x":674,"y":754,"style":732},"−8",[642,766],{"x":678,"y":751,"width":679,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,768,601],{"x":682,"y":754,"style":732},[642,770],{"x":686,"y":751,"width":687,"height":695,"rx":612,"fill":771,"stroke":772,"style":704},"#fce9e9","var(--danger,#dc2626)",[662,774,764],{"x":690,"y":754,"style":775},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[642,777],{"x":650,"y":778,"width":652,"height":695,"rx":612,"fill":654,"stroke":655,"style":656},"124.0",[662,780,420],{"x":698,"y":781,"style":666},"143.0",[642,783],{"x":659,"y":778,"width":660,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,785,786],{"x":664,"y":781,"style":732},"2026-03",[642,788],{"x":670,"y":778,"width":671,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,790,137],{"x":674,"y":781,"style":732},[642,792],{"x":678,"y":778,"width":679,"height":695,"rx":612,"fill":740,"stroke":741,"style":704},[662,794,137],{"x":682,"y":781,"style":744},[642,796],{"x":686,"y":778,"width":687,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,798,601],{"x":690,"y":781,"style":732},[642,800],{"x":650,"y":801,"width":652,"height":695,"rx":612,"fill":654,"stroke":655,"style":656},"152.0",[662,803,137],{"x":698,"y":804,"style":666},"171.0",[642,806],{"x":659,"y":801,"width":660,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,808,809],{"x":664,"y":804,"style":732},"2026-04",[642,811],{"x":670,"y":801,"width":671,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,813,814],{"x":674,"y":804,"style":732},"−15",[642,816],{"x":678,"y":801,"width":679,"height":695,"rx":612,"fill":647,"stroke":655,"style":656},[662,818,601],{"x":682,"y":804,"style":732},[642,820],{"x":686,"y":801,"width":687,"height":695,"rx":612,"fill":771,"stroke":772,"style":704},[662,822,814],{"x":690,"y":804,"style":775},[25,824,826],{"id":825},"method-2-colour-individual-data-points","Method 2 — Colour individual data points",[30,828,830],{"className":67,"code":829,"language":69,"meta":35,"style":35},"from openpyxl.chart.marker import DataPoint\n\nchart2 = BarChart()\nchart2.type = \"col\"\nchart2.add_data(Reference(ws, min_col=2, min_row=1, max_row=len(variance) + 1), titles_from_data=True)\nchart2.set_categories(Reference(ws, min_col=1, min_row=2, max_row=len(variance) + 1))\nseries = chart2.series[0]\nseries.graphicalProperties.solidFill = \"2E7D32\"\nfor idx, v in enumerate(variance):\n    if v \u003C 0:\n        point = DataPoint(idx=idx)\n        point.graphicalProperties.solidFill = \"C62828\"\n        series.dPt.append(point)\nchart2.title = \"Variance to budget (£k) — point colours\"\nchart2.legend = None\nchart2.x_axis.delete = chart2.y_axis.delete = False\nws.add_chart(chart2, \"F20\")\nwb.save(\"variance_split.xlsx\")\nprint(\"red points:\", [p.idx for p in series.dPt])\n",[37,831,832,844,848,857,866,909,944,958,967,981,998,1016,1025,1030,1040,1050,1064,1074,1082],{"__ignoreMap":35},[40,833,834,836,839,841],{"class":42,"line":43},[40,835,77],{"class":76},[40,837,838],{"class":80}," openpyxl.chart.marker ",[40,840,84],{"class":76},[40,842,843],{"class":80}," DataPoint\n",[40,845,846],{"class":42,"line":90},[40,847,107],{"emptyLinePlaceholder":106},[40,849,850,853,855],{"class":42,"line":103},[40,851,852],{"class":80},"chart2 ",[40,854,116],{"class":76},[40,856,360],{"class":80},[40,858,859,862,864],{"class":42,"line":110},[40,860,861],{"class":80},"chart2.type ",[40,863,116],{"class":76},[40,865,371],{"class":50},[40,867,868,871,873,875,877,879,881,883,885,887,889,891,893,895,897,899,901,903,905,907],{"class":42,"line":172},[40,869,870],{"class":80},"chart2.add_data(Reference(ws, ",[40,872,406],{"class":250},[40,874,116],{"class":76},[40,876,256],{"class":122},[40,878,126],{"class":80},[40,880,425],{"class":250},[40,882,116],{"class":76},[40,884,430],{"class":122},[40,886,126],{"class":80},[40,888,435],{"class":250},[40,890,116],{"class":76},[40,892,440],{"class":122},[40,894,443],{"class":80},[40,896,446],{"class":76},[40,898,283],{"class":122},[40,900,451],{"class":80},[40,902,454],{"class":250},[40,904,116],{"class":76},[40,906,459],{"class":122},[40,908,462],{"class":80},[40,910,911,914,916,918,920,922,924,926,928,930,932,934,936,938,940,942],{"class":42,"line":183},[40,912,913],{"class":80},"chart2.set_categories(Reference(ws, ",[40,915,406],{"class":250},[40,917,116],{"class":76},[40,919,430],{"class":122},[40,921,126],{"class":80},[40,923,425],{"class":250},[40,925,116],{"class":76},[40,927,256],{"class":122},[40,929,126],{"class":80},[40,931,435],{"class":250},[40,933,116],{"class":76},[40,935,440],{"class":122},[40,937,443],{"class":80},[40,939,446],{"class":76},[40,941,283],{"class":122},[40,943,499],{"class":80},[40,945,946,949,951,954,956],{"class":42,"line":194},[40,947,948],{"class":80},"series ",[40,950,116],{"class":76},[40,952,953],{"class":80}," chart2.series[",[40,955,612],{"class":122},[40,957,169],{"class":80},[40,959,960,963,965],{"class":42,"line":205},[40,961,962],{"class":80},"series.graphicalProperties.solidFill ",[40,964,116],{"class":76},[40,966,521],{"class":50},[40,968,969,971,974,976,978],{"class":42,"line":232},[40,970,235],{"class":76},[40,972,973],{"class":80}," idx, v ",[40,975,241],{"class":76},[40,977,244],{"class":122},[40,979,980],{"class":80},"(variance):\n",[40,982,983,986,989,992,995],{"class":42,"line":262},[40,984,985],{"class":76},"    if",[40,987,988],{"class":80}," v ",[40,990,991],{"class":76},"\u003C",[40,993,994],{"class":122}," 0",[40,996,997],{"class":80},":\n",[40,999,1000,1003,1005,1008,1011,1013],{"class":42,"line":347},[40,1001,1002],{"class":80},"        point ",[40,1004,116],{"class":76},[40,1006,1007],{"class":80}," DataPoint(",[40,1009,1010],{"class":250},"idx",[40,1012,116],{"class":76},[40,1014,1015],{"class":80},"idx)\n",[40,1017,1018,1021,1023],{"class":42,"line":352},[40,1019,1020],{"class":80},"        point.graphicalProperties.solidFill ",[40,1022,116],{"class":76},[40,1024,532],{"class":50},[40,1026,1027],{"class":42,"line":363},[40,1028,1029],{"class":80},"        series.dPt.append(point)\n",[40,1031,1032,1035,1037],{"class":42,"line":374},[40,1033,1034],{"class":80},"chart2.title ",[40,1036,116],{"class":76},[40,1038,1039],{"class":50}," \"Variance to budget (£k) — point colours\"\n",[40,1041,1042,1045,1047],{"class":42,"line":385},[40,1043,1044],{"class":80},"chart2.legend ",[40,1046,116],{"class":76},[40,1048,1049],{"class":122}," None\n",[40,1051,1052,1055,1057,1060,1062],{"class":42,"line":400},[40,1053,1054],{"class":80},"chart2.x_axis.delete ",[40,1056,116],{"class":76},[40,1058,1059],{"class":80}," chart2.y_axis.delete ",[40,1061,116],{"class":76},[40,1063,559],{"class":122},[40,1065,1066,1069,1072],{"class":42,"line":465},[40,1067,1068],{"class":80},"ws.add_chart(chart2, ",[40,1070,1071],{"class":50},"\"F20\"",[40,1073,462],{"class":80},[40,1075,1076,1078,1080],{"class":42,"line":502},[40,1077,576],{"class":80},[40,1079,579],{"class":50},[40,1081,462],{"class":80},[40,1083,1084,1086,1088,1091,1094,1096,1099,1101],{"class":42,"line":513},[40,1085,587],{"class":122},[40,1087,590],{"class":80},[40,1089,1090],{"class":50},"\"red points:\"",[40,1092,1093],{"class":80},", [p.idx ",[40,1095,235],{"class":76},[40,1097,1098],{"class":80}," p ",[40,1100,241],{"class":76},[40,1102,1103],{"class":80}," series.dPt])\n",[10,1105,1106,1107,1110],{},"A chart series can override the formatting of single points. Here the series is green, and each point whose value is negative gets a ",[37,1108,1109],{},"DataPoint"," with a red fill, identified by its zero-based position in the series. This keeps the chart to one series and needs no helper columns, which is tidy when the chart is produced fresh every time from data Python already has. The catch is that the colours are fixed when the file is written: if someone corrects a value in the workbook and it changes sign, the bar keeps its old colour. Use point colours for generated, read-only reports, and the helper-series method for workbooks people edit.",[25,1112,1114],{"id":1113},"method-3-invert-if-negative-with-a-colour-in-xlsxwriter","Method 3 — Invert if negative, with a colour, in XlsxWriter",[30,1116,1118],{"className":67,"code":1117,"language":69,"meta":35,"style":35},"import xlsxwriter\n\nxw = xlsxwriter.Workbook(\"variance_invert.xlsx\")\nsh = xw.add_worksheet(\"Variance\")\nsh.write_column(\"A1\", [\"Month\"] + [f\"2026-{m:02d}\" for m in range(1, 9)])\nsh.write_column(\"B1\", [\"Variance\"] + variance)\nch = xw.add_chart({\"type\": \"column\"})\nch.add_series({\"name\": \"=Variance!$B$1\", \"categories\": \"=Variance!$A$2:$A$9\", \"values\": \"=Variance!$B$2:$B$9\",\n               \"fill\": {\"color\": \"#2E7D32\"}, \"invert_if_negative\": True, \"invert_if_negative_color\": \"#C62828\"})\nch.set_title({\"name\": \"Variance to budget (£k) — invert if negative\"})\nch.set_legend({\"none\": True})\nsh.insert_chart(\"D2\", ch)\nxw.close()\nprint(\"saved\")\n",[37,1119,1120,1126,1130,1145,1159,1216,1234,1256,1292,1330,1344,1358,1369,1374],{"__ignoreMap":35},[40,1121,1122,1124],{"class":42,"line":43},[40,1123,84],{"class":76},[40,1125,57],{"class":80},[40,1127,1128],{"class":42,"line":90},[40,1129,107],{"emptyLinePlaceholder":106},[40,1131,1132,1135,1137,1140,1143],{"class":42,"line":103},[40,1133,1134],{"class":80},"xw ",[40,1136,116],{"class":76},[40,1138,1139],{"class":80}," xlsxwriter.Workbook(",[40,1141,1142],{"class":50},"\"variance_invert.xlsx\"",[40,1144,462],{"class":80},[40,1146,1147,1150,1152,1155,1157],{"class":42,"line":110},[40,1148,1149],{"class":80},"sh ",[40,1151,116],{"class":76},[40,1153,1154],{"class":80}," xw.add_worksheet(",[40,1156,216],{"class":50},[40,1158,462],{"class":80},[40,1160,1161,1164,1167,1170,1172,1175,1177,1179,1181,1183,1185,1188,1190,1192,1194,1197,1200,1202,1205,1207,1209,1211,1213],{"class":42,"line":172},[40,1162,1163],{"class":80},"sh.write_column(",[40,1165,1166],{"class":50},"\"A1\"",[40,1168,1169],{"class":80},", [",[40,1171,211],{"class":50},[40,1173,1174],{"class":80},"] ",[40,1176,446],{"class":76},[40,1178,119],{"class":80},[40,1180,268],{"class":76},[40,1182,271],{"class":50},[40,1184,275],{"class":274},[40,1186,1187],{"class":80},"m",[40,1189,286],{"class":76},[40,1191,289],{"class":274},[40,1193,292],{"class":50},[40,1195,1196],{"class":76}," for",[40,1198,1199],{"class":80}," m ",[40,1201,241],{"class":76},[40,1203,1204],{"class":122}," range",[40,1206,590],{"class":80},[40,1208,430],{"class":122},[40,1210,126],{"class":80},[40,1212,166],{"class":122},[40,1214,1215],{"class":80},")])\n",[40,1217,1218,1220,1223,1225,1227,1229,1231],{"class":42,"line":183},[40,1219,1163],{"class":80},[40,1221,1222],{"class":50},"\"B1\"",[40,1224,1169],{"class":80},[40,1226,216],{"class":50},[40,1228,1174],{"class":80},[40,1230,446],{"class":76},[40,1232,1233],{"class":80}," variance)\n",[40,1235,1236,1239,1241,1244,1247,1250,1253],{"class":42,"line":194},[40,1237,1238],{"class":80},"ch ",[40,1240,116],{"class":76},[40,1242,1243],{"class":80}," xw.add_chart({",[40,1245,1246],{"class":50},"\"type\"",[40,1248,1249],{"class":80},": ",[40,1251,1252],{"class":50},"\"column\"",[40,1254,1255],{"class":80},"})\n",[40,1257,1258,1261,1264,1266,1269,1271,1274,1276,1279,1281,1284,1286,1289],{"class":42,"line":205},[40,1259,1260],{"class":80},"ch.add_series({",[40,1262,1263],{"class":50},"\"name\"",[40,1265,1249],{"class":80},[40,1267,1268],{"class":50},"\"=Variance!$B$1\"",[40,1270,126],{"class":80},[40,1272,1273],{"class":50},"\"categories\"",[40,1275,1249],{"class":80},[40,1277,1278],{"class":50},"\"=Variance!$A$2:$A$9\"",[40,1280,126],{"class":80},[40,1282,1283],{"class":50},"\"values\"",[40,1285,1249],{"class":80},[40,1287,1288],{"class":50},"\"=Variance!$B$2:$B$9\"",[40,1290,1291],{"class":80},",\n",[40,1293,1294,1297,1300,1303,1305,1308,1311,1314,1316,1318,1320,1323,1325,1328],{"class":42,"line":232},[40,1295,1296],{"class":50},"               \"fill\"",[40,1298,1299],{"class":80},": {",[40,1301,1302],{"class":50},"\"color\"",[40,1304,1249],{"class":80},[40,1306,1307],{"class":50},"\"#2E7D32\"",[40,1309,1310],{"class":80},"}, ",[40,1312,1313],{"class":50},"\"invert_if_negative\"",[40,1315,1249],{"class":80},[40,1317,459],{"class":122},[40,1319,126],{"class":80},[40,1321,1322],{"class":50},"\"invert_if_negative_color\"",[40,1324,1249],{"class":80},[40,1326,1327],{"class":50},"\"#C62828\"",[40,1329,1255],{"class":80},[40,1331,1332,1335,1337,1339,1342],{"class":42,"line":262},[40,1333,1334],{"class":80},"ch.set_title({",[40,1336,1263],{"class":50},[40,1338,1249],{"class":80},[40,1340,1341],{"class":50},"\"Variance to budget (£k) — invert if negative\"",[40,1343,1255],{"class":80},[40,1345,1346,1349,1352,1354,1356],{"class":42,"line":347},[40,1347,1348],{"class":80},"ch.set_legend({",[40,1350,1351],{"class":50},"\"none\"",[40,1353,1249],{"class":80},[40,1355,459],{"class":122},[40,1357,1255],{"class":80},[40,1359,1360,1363,1366],{"class":42,"line":352},[40,1361,1362],{"class":80},"sh.insert_chart(",[40,1364,1365],{"class":50},"\"D2\"",[40,1367,1368],{"class":80},", ch)\n",[40,1370,1371],{"class":42,"line":363},[40,1372,1373],{"class":80},"xw.close()\n",[40,1375,1376,1378,1380,1382],{"class":42,"line":374},[40,1377,587],{"class":122},[40,1379,590],{"class":80},[40,1381,593],{"class":50},[40,1383,462],{"class":80},[10,1385,1386,1387,1389,1390,617,1393,1396,1397,1400],{},"Excel's own option is ",[14,1388,16],{}," on the series. On its own it does something surprising: negative bars lose their fill and become white with an outline, which on a white background looks like the bar is missing. Choosing the colour of the inverted bars came with Excel 2010 as an extension element in the chart XML. XlsxWriter writes both parts when given ",[37,1391,1392],{},"invert_if_negative",[37,1394,1395],{},"invert_if_negative_color",", so Excel shows green bars above zero and red ones below, and the colours follow the data as it changes. openpyxl exposes only the original flag, ",[37,1398,1399],{},"series.invertIfNegative = True",", and does not write the colour extension, which is why setting that flag alone in openpyxl produces white negative bars in Excel. Applications that do not read the extension fall back to their own behaviour, so for files that will also be opened outside Excel, the helper-series method is the safe choice.",[623,1402,632,1408,632,1411,632,1414,632,1418,632,1425,632,1431,632,1436,632,1440,632,1444,632,1448,632,1452,632,1457,632,1462,632,1466,632,1469,632,1472,632,1475,632,1478,632,1483,632,1487,632,1490,632,1493],{"viewBox":1403,"role":626,"ariaLabelledBy":1404,"xmlns":630,"style":1407},"4 0 752 199",[1405,1406],"neg-methods-t","neg-methods-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[634,1409,1410],{"id":1405},"Three ways to colour negative bars",[638,1412,1413],{"id":1406},"Helper series work everywhere and follow the data but need two extra columns. Point colours need no helper columns but are fixed when the file is written. XlsxWriter's invert-if-negative colour follows the data in one series but relies on an Excel 2010 extension.",[642,1415],{"x":420,"y":612,"width":1416,"height":1417,"fill":647},"752","199",[642,1419],{"x":1420,"y":651,"width":1421,"height":1422,"rx":1423,"fill":740,"stroke":741,"style":1424},"20.0","228.0","167.0","14","stroke-width:2px",[662,1426,1430],{"x":1427,"y":1428,"style":1429},"134.0","44.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Two helper series",[42,1432],{"x1":1433,"y1":1434,"x2":1435,"y2":1434,"stroke":741,"style":656},"36.0","57.0","232.0",[662,1437,1439],{"x":1427,"y":1438,"style":732},"77.0","works in every application",[662,1441,1443],{"x":1427,"y":1442,"style":732},"103.0","colours follow the data",[662,1445,1447],{"x":1427,"y":1446,"style":732},"129.0","legend explains the colours",[662,1449,1451],{"x":1427,"y":1450,"style":732},"155.0","two extra columns",[642,1453],{"x":1454,"y":651,"width":1421,"height":1422,"rx":1423,"fill":1455,"stroke":1456,"style":1424},"266.0","#fdefd8","var(--gold,#b4740a)",[662,1458,1461],{"x":1459,"y":1428,"style":1460},"380.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Point colours",[42,1463],{"x1":1464,"y1":1434,"x2":1465,"y2":1434,"stroke":1456,"style":656},"282.0","478.0",[662,1467,1468],{"x":1459,"y":1438,"style":732},"one series, no helpers",[662,1470,1471],{"x":1459,"y":1442,"style":732},"colours fixed at write time",[662,1473,1474],{"x":1459,"y":1446,"style":732},"good for generated reports",[642,1476],{"x":1477,"y":651,"width":1421,"height":1422,"rx":1423,"fill":702,"stroke":703,"style":1424},"512.0",[662,1479,1482],{"x":1480,"y":1428,"style":1481},"626.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Invert if negative + colour",[42,1484],{"x1":1485,"y1":1434,"x2":1486,"y2":1434,"stroke":703,"style":656},"528.0","724.0",[662,1488,1489],{"x":1480,"y":1438,"style":732},"one series, follows the data",[662,1491,1492],{"x":1480,"y":1442,"style":732},"XlsxWriter only",[662,1494,1495],{"x":1480,"y":1446,"style":732},"Excel 2010+ extension",[623,1497,632,1503,632,1506,632,1509,632,1514,632,1518,632,1523,632,1528,632,1532,632,1537,632,1540,632,1545,632,1549,632,1553,632,1556,632,1559,632,1564],{"viewBox":1498,"role":626,"ariaLabelledBy":1499,"xmlns":630,"style":1502},"84 0 592 281",[1500,1501],"neg-invert-t","neg-invert-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[634,1504,1505],{"id":1500},"What invert if negative writes into the chart",[638,1507,1508],{"id":1501},"The series has a normal green fill and the invertIfNegative flag. On its own the flag makes Excel draw negative bars without fill. The Excel 2010 extension invertSolidFillFmt adds the red fill used for negative bars, and XlsxWriter writes it when given invert_if_negative_color.",[642,1510],{"x":1511,"y":612,"width":1512,"height":1513,"fill":647},"84","592","281",[642,1515],{"x":1516,"y":651,"width":1517,"height":699,"rx":123,"fill":740,"stroke":741,"style":1424},"100.0","560.0",[662,1519,1522],{"x":1459,"y":1520,"style":1521},"41.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Series fill",[662,1524,1527],{"x":1459,"y":1525,"style":1526},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","green for every bar",[42,1529],{"x1":1459,"y1":1530,"x2":1459,"y2":1531,"stroke":703,"style":1424},"80.0","102.0",[1533,1534],"polygon",{"points":1535,"fill":1536},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[642,1538],{"x":1516,"y":1539,"width":1517,"height":699,"rx":123,"fill":1455,"stroke":1456,"style":1424},"111.0",[662,1541,1544],{"x":1459,"y":1542,"style":1543},"136.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","invertIfNegative = 1",[662,1546,1548],{"x":1459,"y":1547,"style":1526},"156.0","negative bars lose their fill",[42,1550],{"x1":1459,"y1":1551,"x2":1459,"y2":1552,"stroke":703,"style":1424},"175.0","197.0",[1533,1554],{"points":1555,"fill":1536},"380.0,201.0 375.0,192.0 385.0,192.0",[642,1557],{"x":1516,"y":1558,"width":1517,"height":699,"rx":123,"fill":771,"stroke":772,"style":1424},"206.0",[662,1560,1563],{"x":1459,"y":1561,"style":1562},"231.5","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","invertSolidFillFmt extension",[662,1565,1567],{"x":1459,"y":1566,"style":1526},"251.0","red fill for negative bars",[25,1569,1571],{"id":1570},"choosing-the-colours","Choosing the colours",[10,1573,1574],{},"Red and green are the conventional pair for bad and good, but about one man in twelve has some form of red–green colour blindness and sees the two as similar shades. For charts that will be widely read, a blue and orange pair, or green and red at clearly different lightness, keeps the distinction visible to everyone. The bars' position above and below zero already carries the meaning, so the colour only has to reinforce it — and a clear zero line, with the category labels moved to the bottom of the chart so they do not sit among the negative bars, helps as much as the colour does.",[25,1576,1578],{"id":1577},"check-what-was-written","Check what was written",[30,1580,1582],{"className":67,"code":1581,"language":69,"meta":35,"style":35},"import re, zipfile\n\nwith zipfile.ZipFile(\"variance_split.xlsx\") as z:\n    c1 = z.read(\"xl\u002Fcharts\u002Fchart1.xml\").decode()\n    c2 = z.read(\"xl\u002Fcharts\u002Fchart2.xml\").decode()\nwith zipfile.ZipFile(\"variance_invert.xlsx\") as z:\n    c3 = z.read(\"xl\u002Fcharts\u002Fchart1.xml\").decode()\nprint(\"helper series:\", len(re.findall(r\"\u003Cc:ser>|\u003Cser>\", c1)), \"| overlap:\", re.search(r'overlap val=\"(-?\\d+)\"', c1).group(1))\nprint(\"data points coloured:\", len(re.findall(r\"\u003Cc:dPt>|\u003CdPt>\", c2)))\nprint(\"invert flag:\", 'invertIfNegative val=\"1\"' in c3, \"| colour extension:\", \"invertSolidFillFmt\" in c3)\n",[37,1583,1584,1591,1595,1614,1630,1644,1658,1671,1738,1766],{"__ignoreMap":35},[40,1585,1586,1588],{"class":42,"line":43},[40,1587,84],{"class":76},[40,1589,1590],{"class":80}," re, zipfile\n",[40,1592,1593],{"class":42,"line":90},[40,1594,107],{"emptyLinePlaceholder":106},[40,1596,1597,1600,1603,1605,1608,1611],{"class":42,"line":103},[40,1598,1599],{"class":76},"with",[40,1601,1602],{"class":80}," zipfile.ZipFile(",[40,1604,579],{"class":50},[40,1606,1607],{"class":80},") ",[40,1609,1610],{"class":76},"as",[40,1612,1613],{"class":80}," z:\n",[40,1615,1616,1619,1621,1624,1627],{"class":42,"line":110},[40,1617,1618],{"class":80},"    c1 ",[40,1620,116],{"class":76},[40,1622,1623],{"class":80}," z.read(",[40,1625,1626],{"class":50},"\"xl\u002Fcharts\u002Fchart1.xml\"",[40,1628,1629],{"class":80},").decode()\n",[40,1631,1632,1635,1637,1639,1642],{"class":42,"line":172},[40,1633,1634],{"class":80},"    c2 ",[40,1636,116],{"class":76},[40,1638,1623],{"class":80},[40,1640,1641],{"class":50},"\"xl\u002Fcharts\u002Fchart2.xml\"",[40,1643,1629],{"class":80},[40,1645,1646,1648,1650,1652,1654,1656],{"class":42,"line":183},[40,1647,1599],{"class":76},[40,1649,1602],{"class":80},[40,1651,1142],{"class":50},[40,1653,1607],{"class":80},[40,1655,1610],{"class":76},[40,1657,1613],{"class":80},[40,1659,1660,1663,1665,1667,1669],{"class":42,"line":194},[40,1661,1662],{"class":80},"    c3 ",[40,1664,116],{"class":76},[40,1666,1623],{"class":80},[40,1668,1626],{"class":50},[40,1670,1629],{"class":80},[40,1672,1673,1675,1677,1680,1682,1684,1687,1690,1693,1696,1699,1702,1705,1708,1710,1713,1715,1717,1720,1723,1725,1728,1731,1734,1736],{"class":42,"line":205},[40,1674,587],{"class":122},[40,1676,590],{"class":80},[40,1678,1679],{"class":50},"\"helper series:\"",[40,1681,126],{"class":80},[40,1683,440],{"class":122},[40,1685,1686],{"class":80},"(re.findall(",[40,1688,1689],{"class":76},"r",[40,1691,1692],{"class":50},"\"\u003Cc:ser>",[40,1694,1695],{"class":76},"|",[40,1697,1698],{"class":50},"\u003Cser>\"",[40,1700,1701],{"class":80},", c1)), ",[40,1703,1704],{"class":50},"\"| overlap:\"",[40,1706,1707],{"class":80},", re.search(",[40,1709,1689],{"class":76},[40,1711,1712],{"class":50},"'overlap val=\"",[40,1714,590],{"class":122},[40,1716,129],{"class":50},[40,1718,1719],{"class":76},"?",[40,1721,1722],{"class":122},"\\d",[40,1724,446],{"class":76},[40,1726,1727],{"class":122},")",[40,1729,1730],{"class":50},"\"'",[40,1732,1733],{"class":80},", c1).group(",[40,1735,430],{"class":122},[40,1737,499],{"class":80},[40,1739,1740,1742,1744,1747,1749,1751,1753,1755,1758,1760,1763],{"class":42,"line":232},[40,1741,587],{"class":122},[40,1743,590],{"class":80},[40,1745,1746],{"class":50},"\"data points coloured:\"",[40,1748,126],{"class":80},[40,1750,440],{"class":122},[40,1752,1686],{"class":80},[40,1754,1689],{"class":76},[40,1756,1757],{"class":50},"\"\u003Cc:dPt>",[40,1759,1695],{"class":76},[40,1761,1762],{"class":50},"\u003CdPt>\"",[40,1764,1765],{"class":80},", c2)))\n",[40,1767,1768,1770,1772,1775,1777,1780,1783,1786,1789,1791,1794,1796],{"class":42,"line":262},[40,1769,587],{"class":122},[40,1771,590],{"class":80},[40,1773,1774],{"class":50},"\"invert flag:\"",[40,1776,126],{"class":80},[40,1778,1779],{"class":50},"'invertIfNegative val=\"1\"'",[40,1781,1782],{"class":76}," in",[40,1784,1785],{"class":80}," c3, ",[40,1787,1788],{"class":50},"\"| colour extension:\"",[40,1790,126],{"class":80},[40,1792,1793],{"class":50},"\"invertSolidFillFmt\"",[40,1795,1782],{"class":76},[40,1797,1798],{"class":80}," c3)\n",[10,1800,1801,1802,1805,1806,1809,1810,1813],{},"Chart options leave no trace in openpyxl or XlsxWriter once the file is saved, so the reliable test is to read the chart XML inside the ",[37,1803,1804],{},".xlsx",". The helper-series chart has two series with an overlap of 100; the point-colour chart has one ",[37,1807,1808],{},"dPt"," element for each of the three negative months; and the XlsxWriter chart has both the invert flag and the ",[37,1811,1812],{},"invertSolidFillFmt"," extension that carries the red colour. A test that checks these strings catches the most common regression — a refactor that drops the colour extension and leaves Excel showing white bars.",[10,1815,1816],{},"A visual check is still worth doing in Excel itself when a report is first set up. Different spreadsheet applications render charts slightly differently, and the chart XML is the contract: if the elements above are present, Excel draws the bars as intended.",[25,1818,1820],{"id":1819},"common-pitfalls","Common pitfalls",[1822,1823,1824,1835,1845,1853,1859],"ul",{},[1825,1826,1827,1834],"li",{},[1828,1829,1830,1833],"strong",{},[37,1831,1832],{},"invertIfNegative"," alone."," Without a colour extension, Excel draws negative bars white.",[1825,1836,1837,1840,1841,1844],{},[1828,1838,1839],{},"Zeros in the helper columns."," Use ",[37,1842,1843],{},"NA()"," so the chart skips the month entirely.",[1825,1846,1847,1852],{},[1828,1848,1849,1850,23],{},"Forgetting ",[37,1851,605],{}," The two series sit side by side, each half width.",[1825,1854,1855,1858],{},[1828,1856,1857],{},"Point colours on editable workbooks."," They do not change when a value changes sign.",[1825,1860,1861,1864],{},[1828,1862,1863],{},"No legend for the colours."," Helper series name themselves; otherwise add a note.",[25,1866,1868],{"id":1867},"conclusion","Conclusion",[10,1870,1871,1872,1874,1875,1877,1878,1880,1881,1883],{},"To show negative bars in a different colour, split the values into two helper series — above and below zero, with ",[37,1873,1843],{}," for the other case — and plot them overlapped at 100 with fixed colours; it works everywhere and follows the data. For generated reports, colouring individual points with ",[37,1876,1109],{}," is simpler. In XlsxWriter, ",[37,1879,1392],{}," with ",[37,1882,1395],{}," does it in one series for Excel 2010 and later. Check the chart XML to make sure the option was written.",[25,1885,1887],{"id":1886},"frequently-asked-questions","Frequently asked questions",[10,1889,1890,1893,1894,23],{},[1828,1891,1892],{},"How do I make negative bars red in an Excel chart from Python?","\nPlot two helper series, one for positive and one for negative values, with overlap 100 and a colour each; or colour negative points individually; or use XlsxWriter's ",[37,1895,1395],{},[10,1897,1898,1901],{},[1828,1899,1900],{},"Why are my negative bars white after setting invertIfNegative in openpyxl?","\nExcel's original invert option removes the fill; the colour for inverted bars is an Excel 2010 extension that openpyxl does not write.",[10,1903,1904,1907,1908,1910],{},[1828,1905,1906],{},"Why use NA() instead of 0 in the helper columns?","\nCharts skip ",[37,1909,601],{},", while zero still creates a bar position and a zero label.",[10,1912,1913,1916],{},[1828,1914,1915],{},"Do point colours update when the data changes?","\nNo; they are fixed per point when the file is written, so a value that changes sign keeps its old colour.",[25,1918,1920],{"id":1919},"related","Related",[1822,1922,1923,1929,1936,1943],{},[1825,1924,1925,1926,1928],{},"Up one level: ",[19,1927,22],{"href":21}," — native Excel charts from Python.",[1825,1930,1931,1935],{},[19,1932,1934],{"href":1933},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl\u002F","Style Chart Colours and Data Labels with openpyxl"," — series and point styling.",[1825,1937,1938,1942],{},[19,1939,1941],{"href":1940},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python\u002F","Build a Waterfall Chart in Excel with Python"," — rises and falls in sequence.",[1825,1944,1945,1949],{},[19,1946,1948],{"href":1947},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fshow-negative-numbers-in-red-with-parentheses\u002F","Show Negative Numbers in Red with Parentheses in Excel"," — the same idea in cells.",[1951,1952,1953],"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":35,"searchDepth":90,"depth":90,"links":1955},[1956,1957,1958,1959,1960,1961,1962,1963,1964,1965],{"id":27,"depth":90,"text":28},{"id":63,"depth":90,"text":64},{"id":825,"depth":90,"text":826},{"id":1113,"depth":90,"text":1114},{"id":1570,"depth":90,"text":1571},{"id":1577,"depth":90,"text":1578},{"id":1819,"depth":90,"text":1820},{"id":1867,"depth":90,"text":1868},{"id":1886,"depth":90,"text":1887},{"id":1919,"depth":90,"text":1920},"2026-10-02","Colour the negative bars of a variance or profit chart red and the positive ones green, from Python: split the values into two helper series that update with the data, colour individual data points when the values are fixed, or use XlsxWriter's invert-if-negative colour for Excel 2010 and later — with what each approach writes into the file, how to check it, and why openpyxl's invertIfNegative on its own turns negative bars white.","md",[1970,1972,1974,1976],{"q":1892,"a":1971},"Plot two helper series, one for positive and one for negative values, with overlap 100 and a colour each; or colour negative points individually; or use XlsxWriter's invert_if_negative_color.",{"q":1900,"a":1973},"Excel's original invert option removes the fill; the colour for inverted bars is an Excel 2010 extension that openpyxl does not write.",{"q":1906,"a":1975},"Charts skip #N\u002FA, while zero still creates a bar position and a zero label.",{"q":1915,"a":1977},"No; they are fixed per point when the file is written, so a value that changes sign keeps its old colour.","excel chart negative bars different colour python",null,{"breadcrumb":1981},[1982,1985,1988],{"name":1983,"item":1984},"Home","\u002F",{"name":1986,"item":1987},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour",{"title":1991,"description":1992},"Show Negative Bars in a Different Colour in Excel with Python","Red negative bars in Excel charts from Python: two helper series with overlap 100, per-point colours with DataPoint, XlsxWriter invert_if_negative_color, and why invertIfNegative alone gives white bars.","show-negative-bars-in-a-different-colour","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-negative-bars-in-a-different-colour\u002Findex",[1996,1997],"charts-and-images","openpyxl","how-to","4mNORKaouSWQPz1byrHtgZKvx8CX6VA9I9U7JZMwIqg",[2001,2005],{"title":2002,"path":2003,"stem":2004,"children":-1},"Shade a Target Band Behind a Line Chart","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart\u002Findex",{"title":1934,"path":2006,"stem":2007,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl\u002Findex",[2009,2011],{"path":2010,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":2012,"title":1986},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2015,2018,2021,2024,2027,2030],{"path":2016,"title":2017,"kind":1998},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","Build a Dashboard Sheet with Charts from Multiple Tabs",{"path":2019,"title":2020,"kind":1998},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","Build a Rolling 12-Month Chart on an Excel Dashboard",{"path":2022,"title":2023,"kind":1998},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fkeep-charts-and-images-when-filling-an-excel-template","Keep Charts and Images When Filling an Excel Template",{"path":2025,"title":2026,"kind":1998},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","Add a Chart to an Excel File with xlsxwriter",{"path":2028,"title":2029,"kind":1998},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter","Add Sparklines to an Excel Report with xlsxwriter",{"path":2031,"title":1941,"kind":1998},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python",1790972113377]