[{"data":1,"prerenderedAt":2326},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python":2291,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python":2300,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python":2305,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python":2306},{"id":4,"title":5,"body":6,"dateModified":2257,"datePublished":2257,"description":2258,"extension":2259,"faq":2260,"keyword":2269,"kind":2270,"meta":2271,"navigation":269,"path":2280,"seo":2281,"slug":2284,"stem":2285,"topics":2286,"type":2289,"__hash__":2290},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python\u002Findex.md","Build a Waterfall Chart in Excel with Python",{"type":7,"value":8,"toc":2245},"minimark",[9,19,193,198,226,230,233,471,474,478,502,1360,1363,1427,1431,1434,1853,1868,1872,1875,1964,1971,2127,2131,2139,2142,2146,2173,2177,2180,2184,2190,2196,2202,2208,2212,2241],[10,11,12,13,18],"p",{},"A waterfall — or bridge — chart explains a change. Revenue was 500 last year and 570 this year: how much came from new customers, how much was lost to churn, what did price changes add? Each step floats at the level where the previous one ended, increases in one colour, decreases in another, with the opening and closing totals as full bars. Finance teams expect it in every monthly pack. Excel has a built-in waterfall chart type, but the libraries that write Excel files from Python do not support it, so this guide, part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","Building Excel Reports with xlsxwriter",", builds one the classic way: a stacked column chart with an invisible base.",[20,21,29,30,29,34,29,38,29,46,29,54,29,60,29,64,29,68,29,71,29,75,29,78,29,82,29,85,29,89,29,93,29,99,29,103,29,107,29,111,29,115,29,119,29,123,29,132,29,137,29,144,29,149,29,154,29,158,29,164,29,168,29,173,29,177,29,181,29,184,29,189],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 748 252","img",[25,26],"wf-chart-t","wf-chart-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"A revenue bridge from 500 to 570",[35,36,37],"desc",{"id":26},"A waterfall chart with seven bars. Opening revenue of 500 is a full bar. New customers add 180 and upsell adds 60, floating green steps rising to 740. Churn removes 120 and costs remove 90 as red steps, with price changes adding 40 in between. Closing revenue of 570 is a full bar.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","748","252","#ffffff",[47,48],"line",{"x1":49,"y1":50,"x2":51,"y2":50,"stroke":52,"style":53},"54.0","214.0","736.0","var(--line,#cdd5e6)","stroke-width:1px",[55,56,42],"text",{"x":57,"y":58,"style":59},"31.0","218.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[47,61],{"x1":49,"y1":62,"x2":51,"y2":62,"stroke":52,"style":63},"166.5","stroke-width:1px;stroke-dasharray:5 4",[55,65,67],{"x":57,"y":66,"style":59},"170.5","200",[47,69],{"x1":49,"y1":70,"x2":51,"y2":70,"stroke":52,"style":63},"119.0",[55,72,74],{"x":57,"y":73,"style":59},"123.0","400",[47,76],{"x1":49,"y1":77,"x2":51,"y2":77,"stroke":52,"style":63},"71.5",[55,79,81],{"x":57,"y":80,"style":59},"75.5","600",[47,83],{"x1":49,"y1":84,"x2":51,"y2":84,"stroke":52,"style":63},"24.0",[55,86,88],{"x":57,"y":87,"style":59},"28.0","800",[47,90],{"x1":49,"y1":50,"x2":51,"y2":50,"stroke":91,"style":92},"var(--muted,#5b6780)","stroke-width:1.5px",[55,94,98],{"x":95,"y":96,"style":97},"102.7","232.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Opening",[55,100,102],{"x":101,"y":96,"style":97},"200.2","New cust.",[55,104,106],{"x":105,"y":96,"style":97},"297.6","Upsell",[55,108,110],{"x":109,"y":96,"style":97},"395.0","Churn",[55,112,114],{"x":113,"y":96,"style":97},"492.4","Price",[55,116,118],{"x":117,"y":96,"style":97},"589.9","Costs",[55,120,122],{"x":121,"y":96,"style":97},"687.3","Closing",[39,124],{"x":125,"y":126,"width":127,"height":128,"rx":129,"fill":130,"stroke":131,"style":92},"73.7","95.2","58.0","118.8","3","#f0f4ff","var(--brand,#5b5cf0)",[55,133,136],{"x":95,"y":134,"style":135},"89.2","font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","500",[39,138],{"x":139,"y":140,"width":127,"height":141,"rx":129,"fill":142,"stroke":143,"style":92},"171.2","52.5","42.8","#d9f4f1","var(--teal,#0f9488)",[55,145,148],{"x":101,"y":146,"style":147},"46.5","font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","+180",[39,150],{"x":151,"y":152,"width":127,"height":153,"rx":129,"fill":142,"stroke":143,"style":92},"268.6","38.2","14.2",[55,155,157],{"x":105,"y":156,"style":147},"32.2","+60",[39,159],{"x":160,"y":152,"width":127,"height":161,"rx":129,"fill":162,"stroke":163,"style":92},"366.0","28.5","#fce9e9","var(--danger,#dc2626)",[55,165,167],{"x":109,"y":156,"style":166},"font-size:10.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","−120",[39,169],{"x":170,"y":171,"width":127,"height":172,"rx":129,"fill":142,"stroke":143,"style":92},"463.4","57.2","9.5",[55,174,176],{"x":113,"y":175,"style":147},"51.2","+40",[39,178],{"x":179,"y":171,"width":127,"height":180,"rx":129,"fill":162,"stroke":163,"style":92},"560.9","21.4",[55,182,183],{"x":117,"y":175,"style":166},"−90",[39,185],{"x":186,"y":187,"width":127,"height":188,"rx":129,"fill":130,"stroke":131,"style":92},"658.3","78.6","135.4",[55,190,192],{"x":121,"y":191,"style":135},"72.6","570",[194,195,197],"h2",{"id":196},"prerequisites","Prerequisites",[199,200,205],"pre",{"className":201,"code":202,"language":203,"meta":204,"style":204},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install xlsxwriter pandas\n","bash","",[206,207,208],"code",{"__ignoreMap":204},[209,210,212,216,220,223],"span",{"class":47,"line":211},1,[209,213,215],{"class":214},"sMTad","pip",[209,217,219],{"class":218},"srMev"," install",[209,221,222],{"class":218}," xlsxwriter",[209,224,225],{"class":218}," pandas\n",[194,227,229],{"id":228},"step-1-lay-out-the-steps","Step 1 — Lay out the steps",[10,231,232],{},"A bridge is a list of labelled steps: an opening total, the movements, and a closing total. Keep the inputs in two columns — label and value — and mark which rows are totals:",[199,234,238],{"className":235,"code":236,"language":237,"meta":204,"style":204},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nimport xlsxwriter\n\nsteps = pd.DataFrame({\n    \"label\": [\"Opening\", \"New customers\", \"Upsell\", \"Churn\", \"Price changes\", \"Costs\", \"Closing\"],\n    \"value\": [500, 180, 60, -120, 40, -90, None],\n    \"is_total\": [True, False, False, False, False, False, True],\n})\nsteps.loc[6, \"value\"] = steps.loc[:5, \"value\"].sum()\nprint(steps[\"value\"].tolist())         # [500.0, 180.0, 60.0, -120.0, 40.0, -90.0, 570.0]\n","python",[206,239,240,256,264,271,283,329,377,415,421,453],{"__ignoreMap":204},[209,241,242,246,250,253],{"class":47,"line":211},[209,243,245],{"class":244},"s-kum","import",[209,247,249],{"class":248},"skGVy"," pandas ",[209,251,252],{"class":244},"as",[209,254,255],{"class":248}," pd\n",[209,257,259,261],{"class":47,"line":258},2,[209,260,245],{"class":244},[209,262,263],{"class":248}," xlsxwriter\n",[209,265,267],{"class":47,"line":266},3,[209,268,270],{"emptyLinePlaceholder":269},true,"\n",[209,272,274,277,280],{"class":47,"line":273},4,[209,275,276],{"class":248},"steps ",[209,278,279],{"class":244},"=",[209,281,282],{"class":248}," pd.DataFrame({\n",[209,284,286,289,292,295,298,301,303,306,308,311,313,316,318,321,323,326],{"class":47,"line":285},5,[209,287,288],{"class":218},"    \"label\"",[209,290,291],{"class":248},": [",[209,293,294],{"class":218},"\"Opening\"",[209,296,297],{"class":248},", ",[209,299,300],{"class":218},"\"New customers\"",[209,302,297],{"class":248},[209,304,305],{"class":218},"\"Upsell\"",[209,307,297],{"class":248},[209,309,310],{"class":218},"\"Churn\"",[209,312,297],{"class":248},[209,314,315],{"class":218},"\"Price changes\"",[209,317,297],{"class":248},[209,319,320],{"class":218},"\"Costs\"",[209,322,297],{"class":248},[209,324,325],{"class":218},"\"Closing\"",[209,327,328],{"class":248},"],\n",[209,330,332,335,337,340,342,345,347,350,352,355,358,360,363,365,367,370,372,375],{"class":47,"line":331},6,[209,333,334],{"class":218},"    \"value\"",[209,336,291],{"class":248},[209,338,136],{"class":339},"sP0c6",[209,341,297],{"class":248},[209,343,344],{"class":339},"180",[209,346,297],{"class":248},[209,348,349],{"class":339},"60",[209,351,297],{"class":248},[209,353,354],{"class":244},"-",[209,356,357],{"class":339},"120",[209,359,297],{"class":248},[209,361,362],{"class":339},"40",[209,364,297],{"class":248},[209,366,354],{"class":244},[209,368,369],{"class":339},"90",[209,371,297],{"class":248},[209,373,374],{"class":339},"None",[209,376,328],{"class":248},[209,378,380,383,385,388,390,393,395,397,399,401,403,405,407,409,411,413],{"class":47,"line":379},7,[209,381,382],{"class":218},"    \"is_total\"",[209,384,291],{"class":248},[209,386,387],{"class":339},"True",[209,389,297],{"class":248},[209,391,392],{"class":339},"False",[209,394,297],{"class":248},[209,396,392],{"class":339},[209,398,297],{"class":248},[209,400,392],{"class":339},[209,402,297],{"class":248},[209,404,392],{"class":339},[209,406,297],{"class":248},[209,408,392],{"class":339},[209,410,297],{"class":248},[209,412,387],{"class":339},[209,414,328],{"class":248},[209,416,418],{"class":47,"line":417},8,[209,419,420],{"class":248},"})\n",[209,422,424,427,430,432,435,438,440,443,446,448,450],{"class":47,"line":423},9,[209,425,426],{"class":248},"steps.loc[",[209,428,429],{"class":339},"6",[209,431,297],{"class":248},[209,433,434],{"class":218},"\"value\"",[209,436,437],{"class":248},"] ",[209,439,279],{"class":244},[209,441,442],{"class":248}," steps.loc[:",[209,444,445],{"class":339},"5",[209,447,297],{"class":248},[209,449,434],{"class":218},[209,451,452],{"class":248},"].sum()\n",[209,454,456,459,462,464,467],{"class":47,"line":455},10,[209,457,458],{"class":339},"print",[209,460,461],{"class":248},"(steps[",[209,463,434],{"class":218},[209,465,466],{"class":248},"].tolist())         ",[209,468,470],{"class":469},"s-wDw","# [500.0, 180.0, 60.0, -120.0, 40.0, -90.0, 570.0]\n",[10,472,473],{},"The closing total is the sum of everything above it — computed here so the example is self-consistent, and written as a formula in the workbook in the next step so it stays correct when someone edits a movement.",[194,475,477],{"id":476},"step-2-helper-columns-that-do-the-geometry","Step 2 — Helper columns that do the geometry",[10,479,480,481,485,486,485,489,492,493,496,497,501],{},"The chart needs four series per row: an invisible ",[482,483,484],"strong",{},"base",", the ",[482,487,488],{},"increase",[482,490,491],{},"decrease"," and the ",[482,494,495],{},"total",". They follow from a running total. Write them as formulas, so the chart updates when a user changes a value, and store the computed results with each formula so every viewer sees the right numbers — the reason is explained in ",[14,498,500],{"href":499},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter\u002F","Store Formula Results with xlsxwriter So Every Viewer Shows Values",":",[199,503,505],{"className":235,"code":504,"language":237,"meta":204,"style":204},"running, rows = 0, []\nfor value, is_total in zip(steps[\"value\"], steps[\"is_total\"]):\n    before = running\n    running = value if is_total else running + value\n    rows.append({\"base\": 0 if is_total else min(before, running),\n                 \"up\": 0 if is_total else max(value, 0),\n                 \"down\": 0 if is_total else max(-value, 0),\n                 \"total\": value if is_total else 0,\n                 \"running\": running})\ngeo = pd.DataFrame(rows)\n\nwb = xlsxwriter.Workbook(\"bridge.xlsx\")\nws = wb.add_worksheet(\"Bridge\")\nws.write_row(\"A1\", [\"Step\", \"Value\", \"Running\", \"Base\", \"Increase\", \"Decrease\", \"Total\"])\nn = len(steps)\nfor i in range(n):\n    r = i + 2                                   # Excel row number\n    ws.write(f\"A{r}\", steps.loc[i, \"label\"])\n    if i == n - 1:\n        ws.write_formula(f\"B{r}\", f\"=SUM(B2:B{r - 1})\", None, steps.loc[i, \"value\"])\n    else:\n        ws.write(f\"B{r}\", steps.loc[i, \"value\"])\n    g = geo.loc[i]\n    if steps.loc[i, \"is_total\"]:\n        ws.write_formula(f\"C{r}\", f\"=B{r}\", None, g.running)\n        ws.write_row(f\"D{r}\", [0, 0, 0])\n        ws.write_formula(f\"G{r}\", f\"=B{r}\", None, g.total)\n    else:\n        ws.write_formula(f\"C{r}\", f\"=C{r - 1}+B{r}\", None, g.running)\n        ws.write_formula(f\"D{r}\", f\"=MIN(C{r - 1},C{r})\", None, g.base)\n        ws.write_formula(f\"E{r}\", f\"=MAX(B{r},0)\", None, g.up)\n        ws.write_formula(f\"F{r}\", f\"=MAX(-B{r},0)\", None, g.down)\n        ws.write(f\"G{r}\", 0)\nprint(geo[[\"base\", \"up\", \"down\", \"total\"]].values.tolist()[3])      # [620.0, 0.0, 120.0, 0.0]\n",[206,506,507,520,547,557,585,611,637,666,685,693,703,708,725,741,789,803,819,837,870,892,942,950,974,985,998,1038,1071,1110,1117,1168,1220,1261,1301,1324],{"__ignoreMap":204},[209,508,509,512,514,517],{"class":47,"line":211},[209,510,511],{"class":248},"running, rows ",[209,513,279],{"class":244},[209,515,516],{"class":339}," 0",[209,518,519],{"class":248},", []\n",[209,521,522,525,528,531,534,536,538,541,544],{"class":47,"line":258},[209,523,524],{"class":244},"for",[209,526,527],{"class":248}," value, is_total ",[209,529,530],{"class":244},"in",[209,532,533],{"class":339}," zip",[209,535,461],{"class":248},[209,537,434],{"class":218},[209,539,540],{"class":248},"], steps[",[209,542,543],{"class":218},"\"is_total\"",[209,545,546],{"class":248},"]):\n",[209,548,549,552,554],{"class":47,"line":266},[209,550,551],{"class":248},"    before ",[209,553,279],{"class":244},[209,555,556],{"class":248}," running\n",[209,558,559,562,564,567,570,573,576,579,582],{"class":47,"line":273},[209,560,561],{"class":248},"    running ",[209,563,279],{"class":244},[209,565,566],{"class":248}," value ",[209,568,569],{"class":244},"if",[209,571,572],{"class":248}," is_total ",[209,574,575],{"class":244},"else",[209,577,578],{"class":248}," running ",[209,580,581],{"class":244},"+",[209,583,584],{"class":248}," value\n",[209,586,587,590,593,596,598,601,603,605,608],{"class":47,"line":285},[209,588,589],{"class":248},"    rows.append({",[209,591,592],{"class":218},"\"base\"",[209,594,595],{"class":248},": ",[209,597,42],{"class":339},[209,599,600],{"class":244}," if",[209,602,572],{"class":248},[209,604,575],{"class":244},[209,606,607],{"class":339}," min",[209,609,610],{"class":248},"(before, running),\n",[209,612,613,616,618,620,622,624,626,629,632,634],{"class":47,"line":331},[209,614,615],{"class":218},"                 \"up\"",[209,617,595],{"class":248},[209,619,42],{"class":339},[209,621,600],{"class":244},[209,623,572],{"class":248},[209,625,575],{"class":244},[209,627,628],{"class":339}," max",[209,630,631],{"class":248},"(value, ",[209,633,42],{"class":339},[209,635,636],{"class":248},"),\n",[209,638,639,642,644,646,648,650,652,654,657,659,662,664],{"class":47,"line":379},[209,640,641],{"class":218},"                 \"down\"",[209,643,595],{"class":248},[209,645,42],{"class":339},[209,647,600],{"class":244},[209,649,572],{"class":248},[209,651,575],{"class":244},[209,653,628],{"class":339},[209,655,656],{"class":248},"(",[209,658,354],{"class":244},[209,660,661],{"class":248},"value, ",[209,663,42],{"class":339},[209,665,636],{"class":248},[209,667,668,671,674,676,678,680,682],{"class":47,"line":417},[209,669,670],{"class":218},"                 \"total\"",[209,672,673],{"class":248},": value ",[209,675,569],{"class":244},[209,677,572],{"class":248},[209,679,575],{"class":244},[209,681,516],{"class":339},[209,683,684],{"class":248},",\n",[209,686,687,690],{"class":47,"line":423},[209,688,689],{"class":218},"                 \"running\"",[209,691,692],{"class":248},": running})\n",[209,694,695,698,700],{"class":47,"line":455},[209,696,697],{"class":248},"geo ",[209,699,279],{"class":244},[209,701,702],{"class":248}," pd.DataFrame(rows)\n",[209,704,706],{"class":47,"line":705},11,[209,707,270],{"emptyLinePlaceholder":269},[209,709,711,714,716,719,722],{"class":47,"line":710},12,[209,712,713],{"class":248},"wb ",[209,715,279],{"class":244},[209,717,718],{"class":248}," xlsxwriter.Workbook(",[209,720,721],{"class":218},"\"bridge.xlsx\"",[209,723,724],{"class":248},")\n",[209,726,728,731,733,736,739],{"class":47,"line":727},13,[209,729,730],{"class":248},"ws ",[209,732,279],{"class":244},[209,734,735],{"class":248}," wb.add_worksheet(",[209,737,738],{"class":218},"\"Bridge\"",[209,740,724],{"class":248},[209,742,744,747,750,753,756,758,761,763,766,768,771,773,776,778,781,783,786],{"class":47,"line":743},14,[209,745,746],{"class":248},"ws.write_row(",[209,748,749],{"class":218},"\"A1\"",[209,751,752],{"class":248},", [",[209,754,755],{"class":218},"\"Step\"",[209,757,297],{"class":248},[209,759,760],{"class":218},"\"Value\"",[209,762,297],{"class":248},[209,764,765],{"class":218},"\"Running\"",[209,767,297],{"class":248},[209,769,770],{"class":218},"\"Base\"",[209,772,297],{"class":248},[209,774,775],{"class":218},"\"Increase\"",[209,777,297],{"class":248},[209,779,780],{"class":218},"\"Decrease\"",[209,782,297],{"class":248},[209,784,785],{"class":218},"\"Total\"",[209,787,788],{"class":248},"])\n",[209,790,792,795,797,800],{"class":47,"line":791},15,[209,793,794],{"class":248},"n ",[209,796,279],{"class":244},[209,798,799],{"class":339}," len",[209,801,802],{"class":248},"(steps)\n",[209,804,806,808,811,813,816],{"class":47,"line":805},16,[209,807,524],{"class":244},[209,809,810],{"class":248}," i ",[209,812,530],{"class":244},[209,814,815],{"class":339}," range",[209,817,818],{"class":248},"(n):\n",[209,820,822,825,827,829,831,834],{"class":47,"line":821},17,[209,823,824],{"class":248},"    r ",[209,826,279],{"class":244},[209,828,810],{"class":248},[209,830,581],{"class":244},[209,832,833],{"class":339}," 2",[209,835,836],{"class":469},"                                   # Excel row number\n",[209,838,840,843,846,849,853,856,859,862,865,868],{"class":47,"line":839},18,[209,841,842],{"class":248},"    ws.write(",[209,844,845],{"class":244},"f",[209,847,848],{"class":218},"\"A",[209,850,852],{"class":851},"sSjpA","{",[209,854,855],{"class":248},"r",[209,857,858],{"class":851},"}",[209,860,861],{"class":218},"\"",[209,863,864],{"class":248},", steps.loc[i, ",[209,866,867],{"class":218},"\"label\"",[209,869,788],{"class":248},[209,871,873,876,878,881,884,886,889],{"class":47,"line":872},19,[209,874,875],{"class":244},"    if",[209,877,810],{"class":248},[209,879,880],{"class":244},"==",[209,882,883],{"class":248}," n ",[209,885,354],{"class":244},[209,887,888],{"class":339}," 1",[209,890,891],{"class":248},":\n",[209,893,895,898,900,903,905,907,909,911,913,915,918,920,923,925,927,929,932,934,936,938,940],{"class":47,"line":894},20,[209,896,897],{"class":248},"        ws.write_formula(",[209,899,845],{"class":244},[209,901,902],{"class":218},"\"B",[209,904,852],{"class":851},[209,906,855],{"class":248},[209,908,858],{"class":851},[209,910,861],{"class":218},[209,912,297],{"class":248},[209,914,845],{"class":244},[209,916,917],{"class":218},"\"=SUM(B2:B",[209,919,852],{"class":851},[209,921,922],{"class":248},"r ",[209,924,354],{"class":244},[209,926,888],{"class":339},[209,928,858],{"class":851},[209,930,931],{"class":218},")\"",[209,933,297],{"class":248},[209,935,374],{"class":339},[209,937,864],{"class":248},[209,939,434],{"class":218},[209,941,788],{"class":248},[209,943,945,948],{"class":47,"line":944},21,[209,946,947],{"class":244},"    else",[209,949,891],{"class":248},[209,951,953,956,958,960,962,964,966,968,970,972],{"class":47,"line":952},22,[209,954,955],{"class":248},"        ws.write(",[209,957,845],{"class":244},[209,959,902],{"class":218},[209,961,852],{"class":851},[209,963,855],{"class":248},[209,965,858],{"class":851},[209,967,861],{"class":218},[209,969,864],{"class":248},[209,971,434],{"class":218},[209,973,788],{"class":248},[209,975,977,980,982],{"class":47,"line":976},23,[209,978,979],{"class":248},"    g ",[209,981,279],{"class":244},[209,983,984],{"class":248}," geo.loc[i]\n",[209,986,988,990,993,995],{"class":47,"line":987},24,[209,989,875],{"class":244},[209,991,992],{"class":248}," steps.loc[i, ",[209,994,543],{"class":218},[209,996,997],{"class":248},"]:\n",[209,999,1001,1003,1005,1008,1010,1012,1014,1016,1018,1020,1023,1025,1027,1029,1031,1033,1035],{"class":47,"line":1000},25,[209,1002,897],{"class":248},[209,1004,845],{"class":244},[209,1006,1007],{"class":218},"\"C",[209,1009,852],{"class":851},[209,1011,855],{"class":248},[209,1013,858],{"class":851},[209,1015,861],{"class":218},[209,1017,297],{"class":248},[209,1019,845],{"class":244},[209,1021,1022],{"class":218},"\"=B",[209,1024,852],{"class":851},[209,1026,855],{"class":248},[209,1028,858],{"class":851},[209,1030,861],{"class":218},[209,1032,297],{"class":248},[209,1034,374],{"class":339},[209,1036,1037],{"class":248},", g.running)\n",[209,1039,1041,1044,1046,1049,1051,1053,1055,1057,1059,1061,1063,1065,1067,1069],{"class":47,"line":1040},26,[209,1042,1043],{"class":248},"        ws.write_row(",[209,1045,845],{"class":244},[209,1047,1048],{"class":218},"\"D",[209,1050,852],{"class":851},[209,1052,855],{"class":248},[209,1054,858],{"class":851},[209,1056,861],{"class":218},[209,1058,752],{"class":248},[209,1060,42],{"class":339},[209,1062,297],{"class":248},[209,1064,42],{"class":339},[209,1066,297],{"class":248},[209,1068,42],{"class":339},[209,1070,788],{"class":248},[209,1072,1074,1076,1078,1081,1083,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1105,1107],{"class":47,"line":1073},27,[209,1075,897],{"class":248},[209,1077,845],{"class":244},[209,1079,1080],{"class":218},"\"G",[209,1082,852],{"class":851},[209,1084,855],{"class":248},[209,1086,858],{"class":851},[209,1088,861],{"class":218},[209,1090,297],{"class":248},[209,1092,845],{"class":244},[209,1094,1022],{"class":218},[209,1096,852],{"class":851},[209,1098,855],{"class":248},[209,1100,858],{"class":851},[209,1102,861],{"class":218},[209,1104,297],{"class":248},[209,1106,374],{"class":339},[209,1108,1109],{"class":248},", g.total)\n",[209,1111,1113,1115],{"class":47,"line":1112},28,[209,1114,947],{"class":244},[209,1116,891],{"class":248},[209,1118,1120,1122,1124,1126,1128,1130,1132,1134,1136,1138,1141,1143,1145,1147,1149,1151,1154,1156,1158,1160,1162,1164,1166],{"class":47,"line":1119},29,[209,1121,897],{"class":248},[209,1123,845],{"class":244},[209,1125,1007],{"class":218},[209,1127,852],{"class":851},[209,1129,855],{"class":248},[209,1131,858],{"class":851},[209,1133,861],{"class":218},[209,1135,297],{"class":248},[209,1137,845],{"class":244},[209,1139,1140],{"class":218},"\"=C",[209,1142,852],{"class":851},[209,1144,922],{"class":248},[209,1146,354],{"class":244},[209,1148,888],{"class":339},[209,1150,858],{"class":851},[209,1152,1153],{"class":218},"+B",[209,1155,852],{"class":851},[209,1157,855],{"class":248},[209,1159,858],{"class":851},[209,1161,861],{"class":218},[209,1163,297],{"class":248},[209,1165,374],{"class":339},[209,1167,1037],{"class":248},[209,1169,1171,1173,1175,1177,1179,1181,1183,1185,1187,1189,1192,1194,1196,1198,1200,1202,1205,1207,1209,1211,1213,1215,1217],{"class":47,"line":1170},30,[209,1172,897],{"class":248},[209,1174,845],{"class":244},[209,1176,1048],{"class":218},[209,1178,852],{"class":851},[209,1180,855],{"class":248},[209,1182,858],{"class":851},[209,1184,861],{"class":218},[209,1186,297],{"class":248},[209,1188,845],{"class":244},[209,1190,1191],{"class":218},"\"=MIN(C",[209,1193,852],{"class":851},[209,1195,922],{"class":248},[209,1197,354],{"class":244},[209,1199,888],{"class":339},[209,1201,858],{"class":851},[209,1203,1204],{"class":218},",C",[209,1206,852],{"class":851},[209,1208,855],{"class":248},[209,1210,858],{"class":851},[209,1212,931],{"class":218},[209,1214,297],{"class":248},[209,1216,374],{"class":339},[209,1218,1219],{"class":248},", g.base)\n",[209,1221,1223,1225,1227,1230,1232,1234,1236,1238,1240,1242,1245,1247,1249,1251,1254,1256,1258],{"class":47,"line":1222},31,[209,1224,897],{"class":248},[209,1226,845],{"class":244},[209,1228,1229],{"class":218},"\"E",[209,1231,852],{"class":851},[209,1233,855],{"class":248},[209,1235,858],{"class":851},[209,1237,861],{"class":218},[209,1239,297],{"class":248},[209,1241,845],{"class":244},[209,1243,1244],{"class":218},"\"=MAX(B",[209,1246,852],{"class":851},[209,1248,855],{"class":248},[209,1250,858],{"class":851},[209,1252,1253],{"class":218},",0)\"",[209,1255,297],{"class":248},[209,1257,374],{"class":339},[209,1259,1260],{"class":248},", g.up)\n",[209,1262,1264,1266,1268,1271,1273,1275,1277,1279,1281,1283,1286,1288,1290,1292,1294,1296,1298],{"class":47,"line":1263},32,[209,1265,897],{"class":248},[209,1267,845],{"class":244},[209,1269,1270],{"class":218},"\"F",[209,1272,852],{"class":851},[209,1274,855],{"class":248},[209,1276,858],{"class":851},[209,1278,861],{"class":218},[209,1280,297],{"class":248},[209,1282,845],{"class":244},[209,1284,1285],{"class":218},"\"=MAX(-B",[209,1287,852],{"class":851},[209,1289,855],{"class":248},[209,1291,858],{"class":851},[209,1293,1253],{"class":218},[209,1295,297],{"class":248},[209,1297,374],{"class":339},[209,1299,1300],{"class":248},", g.down)\n",[209,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322],{"class":47,"line":1303},33,[209,1305,955],{"class":248},[209,1307,845],{"class":244},[209,1309,1080],{"class":218},[209,1311,852],{"class":851},[209,1313,855],{"class":248},[209,1315,858],{"class":851},[209,1317,861],{"class":218},[209,1319,297],{"class":248},[209,1321,42],{"class":339},[209,1323,724],{"class":248},[209,1325,1327,1329,1332,1334,1336,1339,1341,1344,1346,1349,1352,1354,1357],{"class":47,"line":1326},34,[209,1328,458],{"class":339},[209,1330,1331],{"class":248},"(geo[[",[209,1333,592],{"class":218},[209,1335,297],{"class":248},[209,1337,1338],{"class":218},"\"up\"",[209,1340,297],{"class":248},[209,1342,1343],{"class":218},"\"down\"",[209,1345,297],{"class":248},[209,1347,1348],{"class":218},"\"total\"",[209,1350,1351],{"class":248},"]].values.tolist()[",[209,1353,129],{"class":339},[209,1355,1356],{"class":248},"])      ",[209,1358,1359],{"class":469},"# [620.0, 0.0, 120.0, 0.0]\n",[10,1361,1362],{},"The base of each step is the lower of the running totals before and after it: an increase starts from where the previous step ended, a decrease from where it will end. Churn is a good check: the running total falls from 740 to 620, so its base is 620 and the red segment on top is 120 tall, spanning exactly 620 to 740.",[20,1364,29,1370,29,1373,29,1376,29,1381,29,1389,29,1400,29,1405,29,1409,29,1411,29,1418,29,1423],{"viewBox":1365,"role":23,"ariaLabelledBy":1366,"xmlns":27,"style":1369},"84 0 592 228",[1367,1368],"wf-segments-t","wf-segments-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1371,1372],{"id":1367},"How one decrease step is drawn",[35,1374,1375],{"id":1368},"The churn step is a stacked column. The lowest segment is the invisible base of 620, the running total after the step. On top sits the red decrease segment of 120, reaching 740, the running total before the step. The increase and total segments for this row are zero.",[39,1377],{"x":1378,"y":42,"width":1379,"height":1380,"fill":45},"84","592","228",[39,1382],{"x":1383,"y":1384,"width":1385,"height":1386,"rx":1387,"fill":162,"stroke":163,"style":1388},"100.0","16.0","560.0","93.0","12","stroke-width:2px",[1390,1391,1393],"g",{"transform":1392},"translate(368.0,29.0) scale(1.000)",[1394,1395],"path",{"d":1396,"fill":1397,"stroke":1398,"style":1399},"M4 7h16 M10 11v6 M14 11v6 M6 7l1 13h10l1-13 M9 7V4h6v3","none","var(--accent-ink,#be185d)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[55,1401,1404],{"x":1402,"y":80,"style":1403},"380.0","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Decrease: 120 (red)",[55,1406,1408],{"x":1402,"y":1407,"style":97},"95.0","620 → 740",[39,1410],{"x":1383,"y":70,"width":1385,"height":1386,"rx":1387,"fill":45,"stroke":52,"style":1388},[1390,1412,1414],{"transform":1413},"translate(368.0,132.0) scale(1.000)",[1394,1415],{"d":1416,"fill":1397,"stroke":1417,"style":1399},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--text,#172033)",[55,1419,1422],{"x":1402,"y":1420,"style":1421},"178.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Base: 620 (no fill)",[55,1424,1426],{"x":1402,"y":1425,"style":97},"198.0","min(before, after)",[194,1428,1430],{"id":1429},"step-3-the-stacked-column-chart","Step 3 — The stacked column chart",[10,1432,1433],{},"Add the four helper columns as stacked series. The base gets no fill and no border, which makes it invisible; the others get their colours. A small function keeps the chart definition in one place:",[199,1435,1437],{"className":235,"code":1436,"language":237,"meta":204,"style":204},"def add_bridge_chart(wb, ws, n, labels=None):\n    chart = wb.add_chart({\"type\": \"column\", \"subtype\": \"stacked\"})\n    categories = [\"Bridge\", 1, 0, n, 0]\n    chart.add_series({\"name\": \"Base\", \"categories\": categories, \"values\": [\"Bridge\", 1, 3, n, 3],\n                      \"fill\": {\"none\": True}, \"border\": {\"none\": True}})\n    colours = {4: (\"Increase\", \"#0B6157\"), 5: (\"Decrease\", \"#B91C1C\"), 6: (\"Total\", \"#4338CA\")}\n    for col, (name, colour) in colours.items():\n        series = {\"name\": name, \"categories\": categories, \"values\": [\"Bridge\", 1, col, n, col],\n                  \"fill\": {\"color\": colour}, \"gap\": 50}\n        if labels:\n            series[\"data_labels\"] = {\"custom\": labels, \"position\": \"inside_end\"}\n        chart.add_series(series)\n    chart.set_title({\"name\": \"Revenue bridge\"})\n    chart.set_legend({\"none\": True})\n    chart.set_y_axis({\"major_gridlines\": {\"visible\": True, \"line\": {\"color\": \"#E7EBEF\"}}})\n    ws.insert_chart(\"I2\", chart, {\"x_scale\": 1.4, \"y_scale\": 1.2})\n    return chart\n",[206,1438,1439,1458,1488,1517,1558,1590,1642,1655,1686,1710,1718,1748,1753,1767,1780,1814,1845],{"__ignoreMap":204},[209,1440,1441,1444,1448,1451,1453,1455],{"class":47,"line":211},[209,1442,1443],{"class":244},"def",[209,1445,1447],{"class":1446},"s_Opv"," add_bridge_chart",[209,1449,1450],{"class":248},"(wb, ws, n, labels",[209,1452,279],{"class":244},[209,1454,374],{"class":339},[209,1456,1457],{"class":248},"):\n",[209,1459,1460,1463,1465,1468,1471,1473,1476,1478,1481,1483,1486],{"class":47,"line":258},[209,1461,1462],{"class":248},"    chart ",[209,1464,279],{"class":244},[209,1466,1467],{"class":248}," wb.add_chart({",[209,1469,1470],{"class":218},"\"type\"",[209,1472,595],{"class":248},[209,1474,1475],{"class":218},"\"column\"",[209,1477,297],{"class":248},[209,1479,1480],{"class":218},"\"subtype\"",[209,1482,595],{"class":248},[209,1484,1485],{"class":218},"\"stacked\"",[209,1487,420],{"class":248},[209,1489,1490,1493,1495,1498,1500,1502,1505,1507,1509,1512,1514],{"class":47,"line":266},[209,1491,1492],{"class":248},"    categories ",[209,1494,279],{"class":244},[209,1496,1497],{"class":248}," [",[209,1499,738],{"class":218},[209,1501,297],{"class":248},[209,1503,1504],{"class":339},"1",[209,1506,297],{"class":248},[209,1508,42],{"class":339},[209,1510,1511],{"class":248},", n, ",[209,1513,42],{"class":339},[209,1515,1516],{"class":248},"]\n",[209,1518,1519,1522,1525,1527,1529,1531,1534,1537,1540,1542,1544,1546,1548,1550,1552,1554,1556],{"class":47,"line":273},[209,1520,1521],{"class":248},"    chart.add_series({",[209,1523,1524],{"class":218},"\"name\"",[209,1526,595],{"class":248},[209,1528,770],{"class":218},[209,1530,297],{"class":248},[209,1532,1533],{"class":218},"\"categories\"",[209,1535,1536],{"class":248},": categories, ",[209,1538,1539],{"class":218},"\"values\"",[209,1541,291],{"class":248},[209,1543,738],{"class":218},[209,1545,297],{"class":248},[209,1547,1504],{"class":339},[209,1549,297],{"class":248},[209,1551,129],{"class":339},[209,1553,1511],{"class":248},[209,1555,129],{"class":339},[209,1557,328],{"class":248},[209,1559,1560,1563,1566,1569,1571,1573,1576,1579,1581,1583,1585,1587],{"class":47,"line":285},[209,1561,1562],{"class":218},"                      \"fill\"",[209,1564,1565],{"class":248},": {",[209,1567,1568],{"class":218},"\"none\"",[209,1570,595],{"class":248},[209,1572,387],{"class":339},[209,1574,1575],{"class":248},"}, ",[209,1577,1578],{"class":218},"\"border\"",[209,1580,1565],{"class":248},[209,1582,1568],{"class":218},[209,1584,595],{"class":248},[209,1586,387],{"class":339},[209,1588,1589],{"class":248},"}})\n",[209,1591,1592,1595,1597,1600,1602,1605,1607,1609,1612,1615,1617,1619,1621,1623,1626,1628,1630,1632,1634,1636,1639],{"class":47,"line":331},[209,1593,1594],{"class":248},"    colours ",[209,1596,279],{"class":244},[209,1598,1599],{"class":248}," {",[209,1601,41],{"class":339},[209,1603,1604],{"class":248},": (",[209,1606,775],{"class":218},[209,1608,297],{"class":248},[209,1610,1611],{"class":218},"\"#0B6157\"",[209,1613,1614],{"class":248},"), ",[209,1616,445],{"class":339},[209,1618,1604],{"class":248},[209,1620,780],{"class":218},[209,1622,297],{"class":248},[209,1624,1625],{"class":218},"\"#B91C1C\"",[209,1627,1614],{"class":248},[209,1629,429],{"class":339},[209,1631,1604],{"class":248},[209,1633,785],{"class":218},[209,1635,297],{"class":248},[209,1637,1638],{"class":218},"\"#4338CA\"",[209,1640,1641],{"class":248},")}\n",[209,1643,1644,1647,1650,1652],{"class":47,"line":379},[209,1645,1646],{"class":244},"    for",[209,1648,1649],{"class":248}," col, (name, colour) ",[209,1651,530],{"class":244},[209,1653,1654],{"class":248}," colours.items():\n",[209,1656,1657,1660,1662,1664,1666,1669,1671,1673,1675,1677,1679,1681,1683],{"class":47,"line":417},[209,1658,1659],{"class":248},"        series ",[209,1661,279],{"class":244},[209,1663,1599],{"class":248},[209,1665,1524],{"class":218},[209,1667,1668],{"class":248},": name, ",[209,1670,1533],{"class":218},[209,1672,1536],{"class":248},[209,1674,1539],{"class":218},[209,1676,291],{"class":248},[209,1678,738],{"class":218},[209,1680,297],{"class":248},[209,1682,1504],{"class":339},[209,1684,1685],{"class":248},", col, n, col],\n",[209,1687,1688,1691,1693,1696,1699,1702,1704,1707],{"class":47,"line":423},[209,1689,1690],{"class":218},"                  \"fill\"",[209,1692,1565],{"class":248},[209,1694,1695],{"class":218},"\"color\"",[209,1697,1698],{"class":248},": colour}, ",[209,1700,1701],{"class":218},"\"gap\"",[209,1703,595],{"class":248},[209,1705,1706],{"class":339},"50",[209,1708,1709],{"class":248},"}\n",[209,1711,1712,1715],{"class":47,"line":455},[209,1713,1714],{"class":244},"        if",[209,1716,1717],{"class":248}," labels:\n",[209,1719,1720,1723,1726,1728,1730,1732,1735,1738,1741,1743,1746],{"class":47,"line":705},[209,1721,1722],{"class":248},"            series[",[209,1724,1725],{"class":218},"\"data_labels\"",[209,1727,437],{"class":248},[209,1729,279],{"class":244},[209,1731,1599],{"class":248},[209,1733,1734],{"class":218},"\"custom\"",[209,1736,1737],{"class":248},": labels, ",[209,1739,1740],{"class":218},"\"position\"",[209,1742,595],{"class":248},[209,1744,1745],{"class":218},"\"inside_end\"",[209,1747,1709],{"class":248},[209,1749,1750],{"class":47,"line":710},[209,1751,1752],{"class":248},"        chart.add_series(series)\n",[209,1754,1755,1758,1760,1762,1765],{"class":47,"line":727},[209,1756,1757],{"class":248},"    chart.set_title({",[209,1759,1524],{"class":218},[209,1761,595],{"class":248},[209,1763,1764],{"class":218},"\"Revenue bridge\"",[209,1766,420],{"class":248},[209,1768,1769,1772,1774,1776,1778],{"class":47,"line":743},[209,1770,1771],{"class":248},"    chart.set_legend({",[209,1773,1568],{"class":218},[209,1775,595],{"class":248},[209,1777,387],{"class":339},[209,1779,420],{"class":248},[209,1781,1782,1785,1788,1790,1793,1795,1797,1799,1802,1804,1806,1808,1811],{"class":47,"line":791},[209,1783,1784],{"class":248},"    chart.set_y_axis({",[209,1786,1787],{"class":218},"\"major_gridlines\"",[209,1789,1565],{"class":248},[209,1791,1792],{"class":218},"\"visible\"",[209,1794,595],{"class":248},[209,1796,387],{"class":339},[209,1798,297],{"class":248},[209,1800,1801],{"class":218},"\"line\"",[209,1803,1565],{"class":248},[209,1805,1695],{"class":218},[209,1807,595],{"class":248},[209,1809,1810],{"class":218},"\"#E7EBEF\"",[209,1812,1813],{"class":248},"}}})\n",[209,1815,1816,1819,1822,1825,1828,1830,1833,1835,1838,1840,1843],{"class":47,"line":805},[209,1817,1818],{"class":248},"    ws.insert_chart(",[209,1820,1821],{"class":218},"\"I2\"",[209,1823,1824],{"class":248},", chart, {",[209,1826,1827],{"class":218},"\"x_scale\"",[209,1829,595],{"class":248},[209,1831,1832],{"class":339},"1.4",[209,1834,297],{"class":248},[209,1836,1837],{"class":218},"\"y_scale\"",[209,1839,595],{"class":248},[209,1841,1842],{"class":339},"1.2",[209,1844,420],{"class":248},[209,1846,1847,1850],{"class":47,"line":821},[209,1848,1849],{"class":244},"    return",[209,1851,1852],{"class":248}," chart\n",[10,1854,1855,1856,1859,1860,1863,1864,1867],{},"The list form ",[206,1857,1858],{},"[\"Bridge\", first_row, col, last_row, col]"," avoids building ",[206,1861,1862],{},"A1","-style strings; rows and columns are zero-based and row 0 is the header. ",[206,1865,1866],{},"gap"," narrows the space between columns, which makes a bridge easier to follow. The legend is hidden because the colours are self-explanatory and \"Base\" should not be advertised.",[194,1869,1871],{"id":1870},"step-4-label-the-steps-with-their-signed-values","Step 4 — Label the steps with their signed values",[10,1873,1874],{},"Bars alone make readers estimate. Label each visible segment with the step's value — including the minus sign for decreases, which the decrease series itself does not contain — using custom data labels that point at the value column:",[199,1876,1878],{"className":235,"code":1877,"language":237,"meta":204,"style":204},"labels = [{\"value\": f\"=Bridge!$B${r}\"} for r in range(2, n + 2)]\nadd_bridge_chart(wb, ws, n, labels)\nwb.close()\nprint(len(labels), labels[3])           # 7 {'value': '=Bridge!$B$5'}\n",[206,1879,1880,1934,1939,1944],{"__ignoreMap":204},[209,1881,1882,1885,1887,1890,1892,1894,1896,1899,1901,1903,1905,1907,1910,1912,1915,1917,1919,1921,1924,1927,1929,1931],{"class":47,"line":211},[209,1883,1884],{"class":248},"labels ",[209,1886,279],{"class":244},[209,1888,1889],{"class":248}," [{",[209,1891,434],{"class":218},[209,1893,595],{"class":248},[209,1895,845],{"class":244},[209,1897,1898],{"class":218},"\"=Bridge!$B$",[209,1900,852],{"class":851},[209,1902,855],{"class":248},[209,1904,858],{"class":851},[209,1906,861],{"class":218},[209,1908,1909],{"class":248},"} ",[209,1911,524],{"class":244},[209,1913,1914],{"class":248}," r ",[209,1916,530],{"class":244},[209,1918,815],{"class":339},[209,1920,656],{"class":248},[209,1922,1923],{"class":339},"2",[209,1925,1926],{"class":248},", n ",[209,1928,581],{"class":244},[209,1930,833],{"class":339},[209,1932,1933],{"class":248},")]\n",[209,1935,1936],{"class":47,"line":258},[209,1937,1938],{"class":248},"add_bridge_chart(wb, ws, n, labels)\n",[209,1940,1941],{"class":47,"line":266},[209,1942,1943],{"class":248},"wb.close()\n",[209,1945,1946,1948,1950,1953,1956,1958,1961],{"class":47,"line":273},[209,1947,458],{"class":339},[209,1949,656],{"class":248},[209,1951,1952],{"class":339},"len",[209,1954,1955],{"class":248},"(labels), labels[",[209,1957,129],{"class":339},[209,1959,1960],{"class":248},"])           ",[209,1962,1963],{"class":469},"# 7 {'value': '=Bridge!$B$5'}\n",[10,1965,1966,1967,1970],{},"Each custom label shows the content of the referenced cell, so the churn segment reads −120 rather than 120, and editing a value in column B updates the label along with the bar. The same list is applied to the increase, decrease and total series; a label on a zero-height segment has nothing to sit on, but check the result in Excel and delete any stray zero labels with ",[206,1968,1969],{},"{\"delete\": True}"," entries in the custom list if your version shows them.",[20,1972,29,1978,29,1981,29,1984,29,1988,29,1993,29,2000,29,2006,29,2010,29,2014,29,2019,29,2024,29,2026,29,2032,29,2036,29,2040,29,2044,29,2047,29,2050,29,2055,29,2059,29,2062,29,2065,29,2068,29,2072,29,2075,29,2078,29,2084,29,2088,29,2091,29,2094,29,2098,29,2101,29,2106,29,2113,29,2118,29,2121,29,2124],{"viewBox":1973,"role":23,"ariaLabelledBy":1974,"xmlns":27,"style":1977},"4 0 752 160",[1975,1976],"wf-flow-t","wf-flow-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1979,1980],{"id":1975},"From a list of movements to a bridge chart",[35,1982,1983],{"id":1976},"List the opening total, the movements and the closing total, compute a running total, derive base, increase, decrease and total helper columns as formulas with stored values, stack them as column series with an unfilled base, and label each step from the value column.",[39,1985],{"x":41,"y":42,"width":1986,"height":1987,"fill":45},"752","160",[39,1989],{"x":1990,"y":1384,"width":1991,"height":1992,"rx":1387,"fill":130,"stroke":131,"style":1388},"20.0","115.2","128.0",[1390,1994,1996],{"transform":1995},"translate(65.6,37.8) scale(1.000)",[1394,1997],{"d":1998,"fill":1397,"stroke":1999,"style":1399},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--brand-strong,#4338ca)",[55,2001,2005],{"x":2002,"y":2003,"style":2004},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","List steps",[55,2007,2009],{"x":2002,"y":2008,"style":97},"103.8","label, value,",[55,2011,2013],{"x":2002,"y":2012,"style":97},"121.2","total?",[47,2015],{"x1":2016,"y1":2017,"x2":2018,"y2":2017,"stroke":131,"style":1388},"140.2","80.0","162.2",[2020,2021],"polygon",{"points":2022,"fill":2023},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[39,2025],{"x":139,"y":1384,"width":1991,"height":1992,"rx":1387,"fill":130,"stroke":131,"style":1388},[1390,2027,2029],{"transform":2028},"translate(216.8,46.5) scale(1.000)",[1394,2030],{"d":2031,"fill":1397,"stroke":1999,"style":1399},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16",[55,2033,2035],{"x":2034,"y":1386,"style":2004},"228.8","Running total",[55,2037,2039],{"x":2034,"y":2038,"style":97},"112.5","formula column",[47,2041],{"x1":2042,"y1":2017,"x2":2043,"y2":2017,"stroke":131,"style":1388},"291.4","313.4",[2020,2045],{"points":2046,"fill":2023},"317.4,80.0 308.4,85.0 308.4,75.0",[39,2048],{"x":2049,"y":1384,"width":1991,"height":1992,"rx":1387,"fill":142,"stroke":143,"style":1388},"322.4",[1390,2051,2052],{"transform":1392},[1394,2053],{"d":1416,"fill":1397,"stroke":2054,"style":1399},"var(--teal-ink,#0b6157)",[55,2056,2058],{"x":1402,"y":80,"style":2057},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Helper",[55,2060,2061],{"x":1402,"y":1386,"style":2057},"columns",[55,2063,2064],{"x":1402,"y":2038,"style":97},"base, up, down,",[55,2066,495],{"x":1402,"y":2067,"style":97},"130.0",[47,2069],{"x1":2070,"y1":2017,"x2":2071,"y2":2017,"stroke":131,"style":1388},"442.6","464.6",[2020,2073],{"points":2074,"fill":2023},"468.6,80.0 459.6,85.0 459.6,75.0",[39,2076],{"x":2077,"y":1384,"width":1991,"height":1992,"rx":1387,"fill":130,"stroke":131,"style":1388},"473.6",[1390,2079,2081],{"transform":2080},"translate(519.2,37.8) scale(1.000)",[1394,2082],{"d":2083,"fill":1397,"stroke":1999,"style":1399},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[55,2085,2087],{"x":2086,"y":2003,"style":2004},"531.2","Stacked",[55,2089,2061],{"x":2086,"y":2090,"style":2004},"101.8",[55,2092,2093],{"x":2086,"y":2012,"style":97},"base has no fill",[47,2095],{"x1":2096,"y1":2017,"x2":2097,"y2":2017,"stroke":131,"style":1388},"593.8","615.8",[2020,2099],{"points":2100,"fill":2023},"619.8,80.0 610.8,85.0 610.8,75.0",[39,2102],{"x":2103,"y":1384,"width":1991,"height":1992,"rx":1387,"fill":2104,"stroke":2105,"style":1388},"624.8","#fdefd8","var(--gold,#b4740a)",[1390,2107,2109],{"transform":2108},"translate(670.4,29.0) scale(1.000)",[1394,2110],{"d":2111,"fill":1397,"stroke":2112,"style":1399},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[55,2114,2117],{"x":2115,"y":80,"style":2116},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Custom",[55,2119,2120],{"x":2115,"y":1386,"style":2116},"labels",[55,2122,2123],{"x":2115,"y":2038,"style":97},"from the value",[55,2125,2126],{"x":2115,"y":2067,"style":97},"column",[194,2128,2130],{"id":2129},"step-5-know-the-limits","Step 5 — Know the limits",[10,2132,2133,2134,2138],{},"The invisible-base technique works while the running total stays above zero, which covers most revenue and cost bridges. When a step takes the running total below zero — a loss-making bridge, a cash balance that goes overdrawn — a stacked column cannot draw a segment that straddles the axis, and the picture breaks. For those, build the chart once with Excel's native waterfall type and let Python update only the data it points at; opening and saving that workbook with openpyxl would drop the native chart, so write the numbers with a tool that leaves charts alone, or keep the chart in a template that the data sheet feeds. Charts in general are covered in ",[14,2135,2137],{"href":2136},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002F","Add a Chart to an Excel File with xlsxwriter",".",[10,2140,2141],{},"Rendering the finished workbook in LibreOffice shows the expected bridge: a blue opening bar at 500, green steps rising to 740, a red step down to 620, a small green step, a red step down to 570 and a blue closing bar at 570. LibreOffice did not draw the cell-referenced custom labels in our test — Excel does — so if the workbook will also be opened in LibreOffice, add the signed values to the category labels (\"Churn −120\") as a fallback.",[194,2143,2145],{"id":2144},"common-pitfalls","Common pitfalls",[2147,2148,2149,2161,2167],"ul",{},[2150,2151,2152,2155,2156,2160],"li",{},[482,2153,2154],{},"Base from the wrong side."," Using the running total ",[2157,2158,2159],"em",{},"before"," a decrease as its base puts the red segment above the previous step. The base is the lower of before and after.",[2150,2162,2163,2166],{},[482,2164,2165],{},"Totals in the running sum."," A subtotal row must reset the running total to its own value, not add to it, or everything after it floats too high.",[2150,2168,2169,2172],{},[482,2170,2171],{},"Hard-coded helper values."," Static helper columns do not follow edited inputs. Use formulas, with stored results.",[194,2174,2176],{"id":2175},"conclusion","Conclusion",[10,2178,2179],{},"Without access to Excel's native waterfall type from Python, build the bridge as a stacked column chart: helper columns for an invisible base, increases, decreases and totals, derived from a running total with formulas that carry stored values, a base series with no fill, colours for up and down, and custom labels that read signed values from the input column. Keep the running total above zero, or use the native chart in a template when it cannot.",[194,2181,2183],{"id":2182},"frequently-asked-questions","Frequently asked questions",[10,2185,2186,2189],{},[482,2187,2188],{},"Can xlsxwriter create Excel's built-in waterfall chart?","\nNo. The waterfall type Excel added in 2016 uses a newer chart format that neither xlsxwriter nor openpyxl writes. The stacked column technique with an invisible base series produces the same picture and works in every Excel version and in LibreOffice.",[10,2191,2192,2195],{},[482,2193,2194],{},"How does the invisible base work?","\nEach step is a stacked column. The bottom segment, the base, is as tall as the lower of the running totals before and after the step and has no fill, so it is invisible; the visible segment on top is the increase or decrease, which therefore appears to float at the right height.",[10,2197,2198,2201],{},[482,2199,2200],{},"How do I show negative steps in red?","\nSplit the steps into two series, increases and decreases, each holding the positive size of the step or zero. Give the increase series a green fill and the decrease series a red one.",[10,2203,2204,2207],{},[482,2205,2206],{},"What if the running total goes below zero?","\nThe stacked technique assumes the running total stays positive, because stacked columns cannot mix negative and positive segments in one bar as a floating range. For bridges that cross zero, build the chart with Excel's native waterfall type in Excel, or use an up-down bar chart.",[194,2209,2211],{"id":2210},"related","Related",[2147,2213,2214,2220,2227,2234],{},[2150,2215,2216,2217,2219],{},"Up one level: ",[14,2218,17],{"href":16}," — report writing with XlsxWriter.",[2150,2221,2222,2226],{},[14,2223,2225],{"href":2224},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-stacked-column-chart-with-openpyxl\u002F","Create a Stacked Column Chart with openpyxl"," — the underlying chart type.",[2150,2228,2229,2233],{},[14,2230,2232],{"href":2231},"\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"," — colouring series and labels.",[2150,2235,2236,2240],{},[14,2237,2239],{"href":2238},"\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"," — formatting the value column.",[2242,2243,2244],"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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":204,"searchDepth":258,"depth":258,"links":2246},[2247,2248,2249,2250,2251,2252,2253,2254,2255,2256],{"id":196,"depth":258,"text":197},{"id":228,"depth":258,"text":229},{"id":476,"depth":258,"text":477},{"id":1429,"depth":258,"text":1430},{"id":1870,"depth":258,"text":1871},{"id":2129,"depth":258,"text":2130},{"id":2144,"depth":258,"text":2145},{"id":2175,"depth":258,"text":2176},{"id":2182,"depth":258,"text":2183},{"id":2210,"depth":258,"text":2211},"2026-09-23","Show how an opening figure becomes a closing one — revenue bridges, budget walks, cash movements — as a waterfall chart built with xlsxwriter. Uses the stacked-column technique with an invisible base, formula-driven helper columns that update when inputs change, green and red steps, total bars and value labels.","md",[2261,2263,2265,2267],{"q":2188,"a":2262},"No. The waterfall type Excel added in 2016 uses a newer chart format that neither xlsxwriter nor openpyxl writes. The stacked column technique with an invisible base series produces the same picture and works in every Excel version and in LibreOffice.",{"q":2194,"a":2264},"Each step is a stacked column. The bottom segment, the base, is as tall as the lower of the running totals before and after the step and has no fill, so it is invisible; the visible segment on top is the increase or decrease, which therefore appears to float at the right height.",{"q":2200,"a":2266},"Split the steps into two series, increases and decreases, each holding the positive size of the step or zero. Give the increase series a green fill and the decrease series a red one.",{"q":2206,"a":2268},"The stacked technique assumes the running total stays positive, because stacked columns cannot mix negative and positive segments in one bar as a floating range. For bridges that cross zero, build the chart with Excel's native waterfall type in Excel, or use an up-down bar chart.","build a waterfall chart in excel with python",null,{"breadcrumb":2272},[2273,2276,2279],{"name":2274,"item":2275},"Home","\u002F",{"name":2277,"item":2278},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":17,"item":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python",{"title":2282,"description":2283},"Build a Waterfall Chart in Excel with Python (xlsxwriter)","Create an Excel waterfall (bridge) chart from Python with xlsxwriter — why the native waterfall type is not available, the invisible-base stacked column method, helper-column formulas with stored values, colours for increases and decreases, custom data labels and the zero-crossing limit.","build-a-waterfall-chart-in-excel-with-python","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python\u002Findex",[2287,2288],"charts-and-images","xlsxwriter","how-to","2CHgeEmlT-FZeibawu5j3QWOFKZy4wGew62pvjCtFSs",[2292,2296],{"title":2293,"path":2294,"stem":2295,"children":-1},"Apply Conditional Formatting with xlsxwriter","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002Findex",{"title":2297,"path":2298,"stem":2299,"children":-1},"Combine Chart Types and Chartsheets with XlsxWriter","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fcombine-chart-types-and-chartsheets-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fcombine-chart-types-and-chartsheets-with-xlsxwriter\u002Findex",[2301,2303],{"path":2302,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter",{"path":2304,"title":2277},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2307,2310,2313,2316,2320,2323],{"path":2308,"title":2309,"kind":2289},"\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":2311,"title":2312,"kind":2289},"\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":2314,"title":2315,"kind":2289},"\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":2317,"title":2318,"kind":2319},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl","Creating Charts in Excel with openpyxl","guide",{"path":2321,"title":2322,"kind":2289},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-combo-chart-with-a-secondary-axis-in-openpyxl","Add a Combo Chart with a Secondary Axis in openpyxl",{"path":2324,"title":2325,"kind":2289},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-data-table-below-an-excel-chart","Add a Data Table Below an Excel Chart",1790972108491]