[{"data":1,"prerenderedAt":1965},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart":1931,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart":1940,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart":1945,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart":1946},{"id":4,"title":5,"body":6,"dateModified":1898,"datePublished":1898,"description":1899,"extension":1900,"faq":1901,"keyword":1910,"kind":1911,"meta":1912,"navigation":193,"path":1920,"seo":1921,"slug":1924,"stem":1925,"topics":1926,"type":1929,"__hash__":1930},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart\u002Findex.md","Shade a Target Band Behind a Line Chart",{"type":7,"value":8,"toc":1885},"minimark",[9,19,24,53,56,60,68,161,165,718,729,733,1055,1061,1065,1216,1232,1236,1418,1491,1494,1676,1680,1687,1760,1764,1802,1806,1812,1816,1822,1832,1842,1848,1852,1881],[10,11,12,13,18],"p",{},"A line chart of a weekly measure answers \"how is it moving?\" but not \"is it where it should be?\". When the measure has a target range rather than a single target — on-time delivery should be between 95% and 98%, stock cover between two and four weeks, call answer time under a ceiling but above a floor — a shaded band behind the line answers the second question at a glance. Points inside the band are fine; the weeks where the line leaves it are the ones to talk about. Excel has no \"band\" option on a line chart, but it can draw one by combining a stacked area chart with a line chart on the same axes, and openpyxl can build that combination. This page does so for a weekly on-time delivery figure, drives the band from two input cells, tidies the legend and lists the weeks outside the band beside the chart. It belongs to ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," openpyxl",[35,51,52],{"class":45}," pandas\n",[10,54,55],{},"LibreOffice is used at the end to render the chart and check it; it is optional.",[20,57,59],{"id":58},"how-the-band-is-drawn","How the band is drawn",[10,61,62,63,67],{},"A stacked area chart draws its second series on top of its first. If the first series is the band's lower limit and is made invisible, and the second is the band's ",[64,65,66],"em",{},"width"," — upper limit minus lower — then the visible part is exactly the strip between the two limits. A line chart with the actual values is then combined with the area chart so both share one set of axes. Because everything is an ordinary Excel chart, readers can restyle it, and it follows the data when the sheet changes.",[69,70,78,79,78,83,78,87,78,95,78,105,78,112,78,117,78,121,78,126,78,131,78,136,78,140,78,144,78,147,78,152,78,157],"svg",{"viewBox":71,"role":72,"ariaLabelledBy":73,"xmlns":76,"style":77},"84 0 592 281","img",[74,75],"band-parts-t","band-parts-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[80,81,82],"title",{"id":74},"How the band is built",[84,85,86],"desc",{"id":75},"An invisible stacked area series fills from the axis up to the lower limit. A light green stacked area series of the band width sits on top of it, covering exactly the target range. The line of actual values is drawn over both on the same axes.",[88,89],"rect",{"x":90,"y":91,"width":92,"height":93,"fill":94},"84","0","592","281","#ffffff",[88,96],{"x":97,"y":98,"width":99,"height":100,"rx":101,"fill":102,"stroke":103,"style":104},"100.0","16.0","560.0","59.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[106,107,111],"text",{"x":108,"y":109,"style":110},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Actual values",[106,113,116],{"x":108,"y":114,"style":115},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","line chart on top",[37,118],{"x1":108,"y1":119,"x2":108,"y2":120,"stroke":103,"style":104},"80.0","102.0",[122,123],"polygon",{"points":124,"fill":125},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[88,127],{"x":97,"y":128,"width":99,"height":100,"rx":101,"fill":129,"stroke":130,"style":104},"111.0","#d9f4f1","var(--teal,#0f9488)",[106,132,135],{"x":108,"y":133,"style":134},"136.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Band width",[106,137,139],{"x":108,"y":138,"style":115},"156.0","upper − lower, light fill",[37,141],{"x1":108,"y1":142,"x2":108,"y2":143,"stroke":103,"style":104},"175.0","197.0",[122,145],{"points":146,"fill":125},"380.0,201.0 375.0,192.0 385.0,192.0",[88,148],{"x":97,"y":149,"width":99,"height":100,"rx":101,"fill":150,"stroke":151,"style":104},"206.0","#e7ebef","var(--line,#cdd5e6)",[106,153,156],{"x":108,"y":154,"style":155},"231.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Lower limit",[106,158,160],{"x":108,"y":159,"style":115},"251.0","stacked area, no fill",[20,162,164],{"id":163},"step-1-data-band-limits-and-helper-columns","Step 1 — Data, band limits and helper columns",[25,166,170],{"className":167,"code":168,"language":169,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\n\nweeks = [f\"W{n}\" for n in range(32, 44)]\non_time = [94.1, 95.3, 96.8, 93.2, 91.5, 95.9, 97.4, 98.6, 96.2, 94.8, 95.5, 97.9]\n\nwb = Workbook()\nws = wb.active\nws.title = \"Delivery\"\nfor ref, label, value in [(\"F\", \"Target low\", 0.95), (\"G\", \"Target high\", 0.98)]:\n    ws[f\"{ref}1\"], ws[f\"{ref}2\"] = label, value\n    ws[f\"{ref}2\"].number_format = \"0%\"\n\nws[\"A1\"], ws[\"B1\"], ws[\"C1\"], ws[\"D1\"] = \"Week\", \"On time\", \"Band low\", \"Target band\"\nfor i, (week, pct) in enumerate(zip(weeks, on_time), start=2):\n    ws.cell(i, 1, week)\n    ws.cell(i, 2, pct \u002F 100).number_format = \"0.0%\"\n    ws.cell(i, 3, \"=$F$2\")                    # lower limit on every row\n    ws.cell(i, 4, \"=$G$2-$F$2\")               # band width on every row\nlast = len(weeks) + 1\nprint(f\"{len(weeks)} weeks, band {ws['F2'].value:.0%}–{ws['G2'].value:.0%}\")\n","python",[32,171,172,188,195,254,325,330,341,352,363,409,453,478,483,529,562,574,598,618,637,657],{"__ignoreMap":30},[35,173,174,178,182,185],{"class":37,"line":38},[35,175,177],{"class":176},"s-kum","from",[35,179,181],{"class":180},"skGVy"," openpyxl ",[35,183,184],{"class":176},"import",[35,186,187],{"class":180}," Workbook\n",[35,189,191],{"class":37,"line":190},2,[35,192,194],{"emptyLinePlaceholder":193},true,"\n",[35,196,198,201,204,207,210,213,217,220,223,226,229,232,235,239,242,245,248,251],{"class":37,"line":197},3,[35,199,200],{"class":180},"weeks ",[35,202,203],{"class":176},"=",[35,205,206],{"class":180}," [",[35,208,209],{"class":176},"f",[35,211,212],{"class":45},"\"W",[35,214,216],{"class":215},"sSjpA","{",[35,218,219],{"class":180},"n",[35,221,222],{"class":215},"}",[35,224,225],{"class":45},"\"",[35,227,228],{"class":176}," for",[35,230,231],{"class":180}," n ",[35,233,234],{"class":176},"in",[35,236,238],{"class":237},"sP0c6"," range",[35,240,241],{"class":180},"(",[35,243,244],{"class":237},"32",[35,246,247],{"class":180},", ",[35,249,250],{"class":237},"44",[35,252,253],{"class":180},")]\n",[35,255,257,260,262,264,267,269,272,274,277,279,282,284,287,289,292,294,297,299,302,304,307,309,312,314,317,319,322],{"class":37,"line":256},4,[35,258,259],{"class":180},"on_time ",[35,261,203],{"class":176},[35,263,206],{"class":180},[35,265,266],{"class":237},"94.1",[35,268,247],{"class":180},[35,270,271],{"class":237},"95.3",[35,273,247],{"class":180},[35,275,276],{"class":237},"96.8",[35,278,247],{"class":180},[35,280,281],{"class":237},"93.2",[35,283,247],{"class":180},[35,285,286],{"class":237},"91.5",[35,288,247],{"class":180},[35,290,291],{"class":237},"95.9",[35,293,247],{"class":180},[35,295,296],{"class":237},"97.4",[35,298,247],{"class":180},[35,300,301],{"class":237},"98.6",[35,303,247],{"class":180},[35,305,306],{"class":237},"96.2",[35,308,247],{"class":180},[35,310,311],{"class":237},"94.8",[35,313,247],{"class":180},[35,315,316],{"class":237},"95.5",[35,318,247],{"class":180},[35,320,321],{"class":237},"97.9",[35,323,324],{"class":180},"]\n",[35,326,328],{"class":37,"line":327},5,[35,329,194],{"emptyLinePlaceholder":193},[35,331,333,336,338],{"class":37,"line":332},6,[35,334,335],{"class":180},"wb ",[35,337,203],{"class":176},[35,339,340],{"class":180}," Workbook()\n",[35,342,344,347,349],{"class":37,"line":343},7,[35,345,346],{"class":180},"ws ",[35,348,203],{"class":176},[35,350,351],{"class":180}," wb.active\n",[35,353,355,358,360],{"class":37,"line":354},8,[35,356,357],{"class":180},"ws.title ",[35,359,203],{"class":176},[35,361,362],{"class":45}," \"Delivery\"\n",[35,364,366,369,372,374,377,380,382,385,387,390,393,396,398,401,403,406],{"class":37,"line":365},9,[35,367,368],{"class":176},"for",[35,370,371],{"class":180}," ref, label, value ",[35,373,234],{"class":176},[35,375,376],{"class":180}," [(",[35,378,379],{"class":45},"\"F\"",[35,381,247],{"class":180},[35,383,384],{"class":45},"\"Target low\"",[35,386,247],{"class":180},[35,388,389],{"class":237},"0.95",[35,391,392],{"class":180},"), (",[35,394,395],{"class":45},"\"G\"",[35,397,247],{"class":180},[35,399,400],{"class":45},"\"Target high\"",[35,402,247],{"class":180},[35,404,405],{"class":237},"0.98",[35,407,408],{"class":180},")]:\n",[35,410,412,415,417,419,421,424,426,429,432,434,436,438,440,442,445,448,450],{"class":37,"line":411},10,[35,413,414],{"class":180},"    ws[",[35,416,209],{"class":176},[35,418,225],{"class":45},[35,420,216],{"class":215},[35,422,423],{"class":180},"ref",[35,425,222],{"class":215},[35,427,428],{"class":45},"1\"",[35,430,431],{"class":180},"], ws[",[35,433,209],{"class":176},[35,435,225],{"class":45},[35,437,216],{"class":215},[35,439,423],{"class":180},[35,441,222],{"class":215},[35,443,444],{"class":45},"2\"",[35,446,447],{"class":180},"] ",[35,449,203],{"class":176},[35,451,452],{"class":180}," label, value\n",[35,454,456,458,460,462,464,466,468,470,473,475],{"class":37,"line":455},11,[35,457,414],{"class":180},[35,459,209],{"class":176},[35,461,225],{"class":45},[35,463,216],{"class":215},[35,465,423],{"class":180},[35,467,222],{"class":215},[35,469,444],{"class":45},[35,471,472],{"class":180},"].number_format ",[35,474,203],{"class":176},[35,476,477],{"class":45}," \"0%\"\n",[35,479,481],{"class":37,"line":480},12,[35,482,194],{"emptyLinePlaceholder":193},[35,484,486,489,492,494,497,499,502,504,507,509,511,514,516,519,521,524,526],{"class":37,"line":485},13,[35,487,488],{"class":180},"ws[",[35,490,491],{"class":45},"\"A1\"",[35,493,431],{"class":180},[35,495,496],{"class":45},"\"B1\"",[35,498,431],{"class":180},[35,500,501],{"class":45},"\"C1\"",[35,503,431],{"class":180},[35,505,506],{"class":45},"\"D1\"",[35,508,447],{"class":180},[35,510,203],{"class":176},[35,512,513],{"class":45}," \"Week\"",[35,515,247],{"class":180},[35,517,518],{"class":45},"\"On time\"",[35,520,247],{"class":180},[35,522,523],{"class":45},"\"Band low\"",[35,525,247],{"class":180},[35,527,528],{"class":45},"\"Target band\"\n",[35,530,532,534,537,539,542,544,547,550,554,556,559],{"class":37,"line":531},14,[35,533,368],{"class":176},[35,535,536],{"class":180}," i, (week, pct) ",[35,538,234],{"class":176},[35,540,541],{"class":237}," enumerate",[35,543,241],{"class":180},[35,545,546],{"class":237},"zip",[35,548,549],{"class":180},"(weeks, on_time), ",[35,551,553],{"class":552},"sa561","start",[35,555,203],{"class":176},[35,557,558],{"class":237},"2",[35,560,561],{"class":180},"):\n",[35,563,565,568,571],{"class":37,"line":564},15,[35,566,567],{"class":180},"    ws.cell(i, ",[35,569,570],{"class":237},"1",[35,572,573],{"class":180},", week)\n",[35,575,577,579,581,584,587,590,593,595],{"class":37,"line":576},16,[35,578,567],{"class":180},[35,580,558],{"class":237},[35,582,583],{"class":180},", pct ",[35,585,586],{"class":176},"\u002F",[35,588,589],{"class":237}," 100",[35,591,592],{"class":180},").number_format ",[35,594,203],{"class":176},[35,596,597],{"class":45}," \"0.0%\"\n",[35,599,601,603,606,608,611,614],{"class":37,"line":600},17,[35,602,567],{"class":180},[35,604,605],{"class":237},"3",[35,607,247],{"class":180},[35,609,610],{"class":45},"\"=$F$2\"",[35,612,613],{"class":180},")                    ",[35,615,617],{"class":616},"s-wDw","# lower limit on every row\n",[35,619,621,623,626,628,631,634],{"class":37,"line":620},18,[35,622,567],{"class":180},[35,624,625],{"class":237},"4",[35,627,247],{"class":180},[35,629,630],{"class":45},"\"=$G$2-$F$2\"",[35,632,633],{"class":180},")               ",[35,635,636],{"class":616},"# band width on every row\n",[35,638,640,643,645,648,651,654],{"class":37,"line":639},19,[35,641,642],{"class":180},"last ",[35,644,203],{"class":176},[35,646,647],{"class":237}," len",[35,649,650],{"class":180},"(weeks) ",[35,652,653],{"class":176},"+",[35,655,656],{"class":237}," 1\n",[35,658,660,663,665,667,669,671,674,677,679,682,684,686,689,692,695,697,700,702,704,707,709,711,713,715],{"class":37,"line":659},20,[35,661,662],{"class":237},"print",[35,664,241],{"class":180},[35,666,209],{"class":176},[35,668,225],{"class":45},[35,670,216],{"class":215},[35,672,673],{"class":237},"len",[35,675,676],{"class":180},"(weeks)",[35,678,222],{"class":215},[35,680,681],{"class":45}," weeks, band ",[35,683,216],{"class":215},[35,685,488],{"class":180},[35,687,688],{"class":45},"'F2'",[35,690,691],{"class":180},"].value",[35,693,694],{"class":176},":.0%",[35,696,222],{"class":215},[35,698,699],{"class":45},"–",[35,701,216],{"class":215},[35,703,488],{"class":180},[35,705,706],{"class":45},"'G2'",[35,708,691],{"class":180},[35,710,694],{"class":176},[35,712,222],{"class":215},[35,714,225],{"class":45},[35,716,717],{"class":180},")\n",[10,719,720,721,724,725,728],{},"The two limits live in their own cells, ",[64,722,723],{},"Target low"," and ",[64,726,727],{},"Target high",", and the helper columns refer to them with formulas. Changing the target range for next quarter is then a matter of typing two numbers; the band in the chart moves with them. The helper columns repeat the same value on every row because a chart series needs one value per category. If the target changes partway through the period — a tighter target from week 40, say — the helper columns can hold different values on different rows, and the band will step accordingly, which is a neat way to show a target change on the chart itself.",[20,730,732],{"id":731},"step-2-the-area-chart-for-the-band-the-line-chart-for-the-values","Step 2 — The area chart for the band, the line chart for the values",[25,734,736],{"className":167,"code":735,"language":169,"meta":30,"style":30},"from openpyxl.chart import AreaChart, LineChart, Reference\nfrom openpyxl.chart.legend import LegendEntry\n\nband_chart = AreaChart()\nband_chart.grouping = \"stacked\"\nband_chart.add_data(Reference(ws, min_col=3, max_col=4, min_row=1, max_row=last), titles_from_data=True)\nband_chart.set_categories(Reference(ws, min_col=1, min_row=2, max_row=last))\nbase, band = band_chart.series\nbase.graphicalProperties.noFill = True                 # invisible lower part\nbase.graphicalProperties.line.noFill = True\nband.graphicalProperties.solidFill = \"D9EAD3\"          # light green band\nband.graphicalProperties.line.noFill = True\n\nline_chart = LineChart()\nline_chart.add_data(Reference(ws, min_col=2, min_row=1, max_row=last), titles_from_data=True)\nactual = line_chart.series[0]\nactual.graphicalProperties.line.solidFill = \"1F5FA8\"\nactual.graphicalProperties.line.width = 28575          # 2.25 pt\nactual.marker.symbol = \"circle\"\nactual.marker.size = 6\nactual.smooth = False\n\nband_chart += line_chart                               # same axes, line drawn on top\n",[32,737,738,750,762,766,776,786,836,864,874,887,897,910,919,923,933,968,982,992,1005,1015,1025,1036,1041],{"__ignoreMap":30},[35,739,740,742,745,747],{"class":37,"line":38},[35,741,177],{"class":176},[35,743,744],{"class":180}," openpyxl.chart ",[35,746,184],{"class":176},[35,748,749],{"class":180}," AreaChart, LineChart, Reference\n",[35,751,752,754,757,759],{"class":37,"line":190},[35,753,177],{"class":176},[35,755,756],{"class":180}," openpyxl.chart.legend ",[35,758,184],{"class":176},[35,760,761],{"class":180}," LegendEntry\n",[35,763,764],{"class":37,"line":197},[35,765,194],{"emptyLinePlaceholder":193},[35,767,768,771,773],{"class":37,"line":256},[35,769,770],{"class":180},"band_chart ",[35,772,203],{"class":176},[35,774,775],{"class":180}," AreaChart()\n",[35,777,778,781,783],{"class":37,"line":327},[35,779,780],{"class":180},"band_chart.grouping ",[35,782,203],{"class":176},[35,784,785],{"class":45}," \"stacked\"\n",[35,787,788,791,794,796,798,800,803,805,807,809,812,814,816,818,821,823,826,829,831,834],{"class":37,"line":332},[35,789,790],{"class":180},"band_chart.add_data(Reference(ws, ",[35,792,793],{"class":552},"min_col",[35,795,203],{"class":176},[35,797,605],{"class":237},[35,799,247],{"class":180},[35,801,802],{"class":552},"max_col",[35,804,203],{"class":176},[35,806,625],{"class":237},[35,808,247],{"class":180},[35,810,811],{"class":552},"min_row",[35,813,203],{"class":176},[35,815,570],{"class":237},[35,817,247],{"class":180},[35,819,820],{"class":552},"max_row",[35,822,203],{"class":176},[35,824,825],{"class":180},"last), ",[35,827,828],{"class":552},"titles_from_data",[35,830,203],{"class":176},[35,832,833],{"class":237},"True",[35,835,717],{"class":180},[35,837,838,841,843,845,847,849,851,853,855,857,859,861],{"class":37,"line":343},[35,839,840],{"class":180},"band_chart.set_categories(Reference(ws, ",[35,842,793],{"class":552},[35,844,203],{"class":176},[35,846,570],{"class":237},[35,848,247],{"class":180},[35,850,811],{"class":552},[35,852,203],{"class":176},[35,854,558],{"class":237},[35,856,247],{"class":180},[35,858,820],{"class":552},[35,860,203],{"class":176},[35,862,863],{"class":180},"last))\n",[35,865,866,869,871],{"class":37,"line":354},[35,867,868],{"class":180},"base, band ",[35,870,203],{"class":176},[35,872,873],{"class":180}," band_chart.series\n",[35,875,876,879,881,884],{"class":37,"line":365},[35,877,878],{"class":180},"base.graphicalProperties.noFill ",[35,880,203],{"class":176},[35,882,883],{"class":237}," True",[35,885,886],{"class":616},"                 # invisible lower part\n",[35,888,889,892,894],{"class":37,"line":411},[35,890,891],{"class":180},"base.graphicalProperties.line.noFill ",[35,893,203],{"class":176},[35,895,896],{"class":237}," True\n",[35,898,899,902,904,907],{"class":37,"line":455},[35,900,901],{"class":180},"band.graphicalProperties.solidFill ",[35,903,203],{"class":176},[35,905,906],{"class":45}," \"D9EAD3\"",[35,908,909],{"class":616},"          # light green band\n",[35,911,912,915,917],{"class":37,"line":480},[35,913,914],{"class":180},"band.graphicalProperties.line.noFill ",[35,916,203],{"class":176},[35,918,896],{"class":237},[35,920,921],{"class":37,"line":485},[35,922,194],{"emptyLinePlaceholder":193},[35,924,925,928,930],{"class":37,"line":531},[35,926,927],{"class":180},"line_chart ",[35,929,203],{"class":176},[35,931,932],{"class":180}," LineChart()\n",[35,934,935,938,940,942,944,946,948,950,952,954,956,958,960,962,964,966],{"class":37,"line":564},[35,936,937],{"class":180},"line_chart.add_data(Reference(ws, ",[35,939,793],{"class":552},[35,941,203],{"class":176},[35,943,558],{"class":237},[35,945,247],{"class":180},[35,947,811],{"class":552},[35,949,203],{"class":176},[35,951,570],{"class":237},[35,953,247],{"class":180},[35,955,820],{"class":552},[35,957,203],{"class":176},[35,959,825],{"class":180},[35,961,828],{"class":552},[35,963,203],{"class":176},[35,965,833],{"class":237},[35,967,717],{"class":180},[35,969,970,973,975,978,980],{"class":37,"line":576},[35,971,972],{"class":180},"actual ",[35,974,203],{"class":176},[35,976,977],{"class":180}," line_chart.series[",[35,979,91],{"class":237},[35,981,324],{"class":180},[35,983,984,987,989],{"class":37,"line":600},[35,985,986],{"class":180},"actual.graphicalProperties.line.solidFill ",[35,988,203],{"class":176},[35,990,991],{"class":45}," \"1F5FA8\"\n",[35,993,994,997,999,1002],{"class":37,"line":620},[35,995,996],{"class":180},"actual.graphicalProperties.line.width ",[35,998,203],{"class":176},[35,1000,1001],{"class":237}," 28575",[35,1003,1004],{"class":616},"          # 2.25 pt\n",[35,1006,1007,1010,1012],{"class":37,"line":639},[35,1008,1009],{"class":180},"actual.marker.symbol ",[35,1011,203],{"class":176},[35,1013,1014],{"class":45}," \"circle\"\n",[35,1016,1017,1020,1022],{"class":37,"line":659},[35,1018,1019],{"class":180},"actual.marker.size ",[35,1021,203],{"class":176},[35,1023,1024],{"class":237}," 6\n",[35,1026,1028,1031,1033],{"class":37,"line":1027},21,[35,1029,1030],{"class":180},"actual.smooth ",[35,1032,203],{"class":176},[35,1034,1035],{"class":237}," False\n",[35,1037,1039],{"class":37,"line":1038},22,[35,1040,194],{"emptyLinePlaceholder":193},[35,1042,1044,1046,1049,1052],{"class":37,"line":1043},23,[35,1045,770],{"class":180},[35,1047,1048],{"class":176},"+=",[35,1050,1051],{"class":180}," line_chart                               ",[35,1053,1054],{"class":616},"# same axes, line drawn on top\n",[10,1056,1057,1058,1060],{},"The area chart is created first and the line chart is added to it with ",[32,1059,1048],{},", which puts both on the same category and value axes; the series added last is drawn on top, so the line is never hidden by the band. The lower series has both its fill and its outline switched off, so it takes up space in the stack without being seen. The band uses a pale fill so it reads as background — the line is the data, the band only context. Green suits a \"good range\"; if the band marks a danger zone, a pale red or grey says so instead.",[20,1062,1064],{"id":1063},"step-3-axes-legend-and-placement","Step 3 — Axes, legend and placement",[25,1066,1068],{"className":167,"code":1067,"language":169,"meta":30,"style":30},"band_chart.title = \"On-time delivery against the target band\"\nband_chart.y_axis.scaling.min, band_chart.y_axis.scaling.max = 0.88, 1.0\nband_chart.y_axis.majorUnit = 0.02\nband_chart.y_axis.number_format = \"0%\"\nband_chart.x_axis.delete = band_chart.y_axis.delete = False\nband_chart.legend.position = \"b\"\nband_chart.legend.legendEntry = [LegendEntry(idx=0, delete=True)]      # hide \"Band low\"\nband_chart.width, band_chart.height = 18, 9\nws.add_chart(band_chart, \"A16\")\nwb.save(\"on_time_delivery.xlsx\")\nprint(\"saved\")\n",[32,1069,1070,1080,1095,1105,1114,1128,1138,1170,1185,1195,1205],{"__ignoreMap":30},[35,1071,1072,1075,1077],{"class":37,"line":38},[35,1073,1074],{"class":180},"band_chart.title ",[35,1076,203],{"class":176},[35,1078,1079],{"class":45}," \"On-time delivery against the target band\"\n",[35,1081,1082,1085,1087,1090,1092],{"class":37,"line":190},[35,1083,1084],{"class":180},"band_chart.y_axis.scaling.min, band_chart.y_axis.scaling.max ",[35,1086,203],{"class":176},[35,1088,1089],{"class":237}," 0.88",[35,1091,247],{"class":180},[35,1093,1094],{"class":237},"1.0\n",[35,1096,1097,1100,1102],{"class":37,"line":197},[35,1098,1099],{"class":180},"band_chart.y_axis.majorUnit ",[35,1101,203],{"class":176},[35,1103,1104],{"class":237}," 0.02\n",[35,1106,1107,1110,1112],{"class":37,"line":256},[35,1108,1109],{"class":180},"band_chart.y_axis.number_format ",[35,1111,203],{"class":176},[35,1113,477],{"class":45},[35,1115,1116,1119,1121,1124,1126],{"class":37,"line":327},[35,1117,1118],{"class":180},"band_chart.x_axis.delete ",[35,1120,203],{"class":176},[35,1122,1123],{"class":180}," band_chart.y_axis.delete ",[35,1125,203],{"class":176},[35,1127,1035],{"class":237},[35,1129,1130,1133,1135],{"class":37,"line":332},[35,1131,1132],{"class":180},"band_chart.legend.position ",[35,1134,203],{"class":176},[35,1136,1137],{"class":45}," \"b\"\n",[35,1139,1140,1143,1145,1148,1151,1153,1155,1157,1160,1162,1164,1167],{"class":37,"line":343},[35,1141,1142],{"class":180},"band_chart.legend.legendEntry ",[35,1144,203],{"class":176},[35,1146,1147],{"class":180}," [LegendEntry(",[35,1149,1150],{"class":552},"idx",[35,1152,203],{"class":176},[35,1154,91],{"class":237},[35,1156,247],{"class":180},[35,1158,1159],{"class":552},"delete",[35,1161,203],{"class":176},[35,1163,833],{"class":237},[35,1165,1166],{"class":180},")]      ",[35,1168,1169],{"class":616},"# hide \"Band low\"\n",[35,1171,1172,1175,1177,1180,1182],{"class":37,"line":354},[35,1173,1174],{"class":180},"band_chart.width, band_chart.height ",[35,1176,203],{"class":176},[35,1178,1179],{"class":237}," 18",[35,1181,247],{"class":180},[35,1183,1184],{"class":237},"9\n",[35,1186,1187,1190,1193],{"class":37,"line":365},[35,1188,1189],{"class":180},"ws.add_chart(band_chart, ",[35,1191,1192],{"class":45},"\"A16\"",[35,1194,717],{"class":180},[35,1196,1197,1200,1203],{"class":37,"line":411},[35,1198,1199],{"class":180},"wb.save(",[35,1201,1202],{"class":45},"\"on_time_delivery.xlsx\"",[35,1204,717],{"class":180},[35,1206,1207,1209,1211,1214],{"class":37,"line":455},[35,1208,662],{"class":237},[35,1210,241],{"class":180},[35,1212,1213],{"class":45},"\"saved\"",[35,1215,717],{"class":180},[10,1217,1218,1219,247,1222,724,1225,1228,1229,1231],{},"The value axis is cropped to 88–100% so a few points of movement are visible; on a 0–100% axis every week would look the same. The axis is set on the area chart, which owns the axes of the combined chart. The legend would list three entries — ",[64,1220,1221],{},"Band low",[64,1223,1224],{},"Target band",[64,1226,1227],{},"On time"," — and the first means nothing to a reader, so it is removed by index, leaving ",[64,1230,1224],{}," to label the shading. Both axes are explicitly kept visible, because openpyxl otherwise writes charts whose axes Excel hides.",[20,1233,1235],{"id":1234},"step-4-list-the-weeks-outside-the-band-and-render-the-chart","Step 4 — List the weeks outside the band, and render the chart",[25,1237,1239],{"className":167,"code":1238,"language":169,"meta":30,"style":30},"low, high = 0.95, 0.98\noutside = [(w, p \u002F 100) for w, p in zip(weeks, on_time) if not low \u003C= p \u002F 100 \u003C= high]\nws[\"F4\"] = \"Weeks outside the band\"\nfor r, (week, value) in enumerate(outside, start=5):\n    ws.cell(r, 6, week)\n    ws.cell(r, 7, value).number_format = \"0.0%\"\n    ws.cell(r, 8, \"below\" if value \u003C low else \"above\")\nwb.save(\"on_time_delivery.xlsx\")\nprint(outside)\n",[32,1240,1241,1256,1311,1325,1348,1358,1372,1403,1411],{"__ignoreMap":30},[35,1242,1243,1246,1248,1251,1253],{"class":37,"line":38},[35,1244,1245],{"class":180},"low, high ",[35,1247,203],{"class":176},[35,1249,1250],{"class":237}," 0.95",[35,1252,247],{"class":180},[35,1254,1255],{"class":237},"0.98\n",[35,1257,1258,1261,1263,1266,1268,1270,1273,1275,1278,1280,1283,1286,1289,1292,1295,1298,1301,1303,1305,1308],{"class":37,"line":190},[35,1259,1260],{"class":180},"outside ",[35,1262,203],{"class":176},[35,1264,1265],{"class":180}," [(w, p ",[35,1267,586],{"class":176},[35,1269,589],{"class":237},[35,1271,1272],{"class":180},") ",[35,1274,368],{"class":176},[35,1276,1277],{"class":180}," w, p ",[35,1279,234],{"class":176},[35,1281,1282],{"class":237}," zip",[35,1284,1285],{"class":180},"(weeks, on_time) ",[35,1287,1288],{"class":176},"if",[35,1290,1291],{"class":176}," not",[35,1293,1294],{"class":180}," low ",[35,1296,1297],{"class":176},"\u003C=",[35,1299,1300],{"class":180}," p ",[35,1302,586],{"class":176},[35,1304,589],{"class":237},[35,1306,1307],{"class":176}," \u003C=",[35,1309,1310],{"class":180}," high]\n",[35,1312,1313,1315,1318,1320,1322],{"class":37,"line":197},[35,1314,488],{"class":180},[35,1316,1317],{"class":45},"\"F4\"",[35,1319,447],{"class":180},[35,1321,203],{"class":176},[35,1323,1324],{"class":45}," \"Weeks outside the band\"\n",[35,1326,1327,1329,1332,1334,1336,1339,1341,1343,1346],{"class":37,"line":256},[35,1328,368],{"class":176},[35,1330,1331],{"class":180}," r, (week, value) ",[35,1333,234],{"class":176},[35,1335,541],{"class":237},[35,1337,1338],{"class":180},"(outside, ",[35,1340,553],{"class":552},[35,1342,203],{"class":176},[35,1344,1345],{"class":237},"5",[35,1347,561],{"class":180},[35,1349,1350,1353,1356],{"class":37,"line":327},[35,1351,1352],{"class":180},"    ws.cell(r, ",[35,1354,1355],{"class":237},"6",[35,1357,573],{"class":180},[35,1359,1360,1362,1365,1368,1370],{"class":37,"line":332},[35,1361,1352],{"class":180},[35,1363,1364],{"class":237},"7",[35,1366,1367],{"class":180},", value).number_format ",[35,1369,203],{"class":176},[35,1371,597],{"class":45},[35,1373,1374,1376,1379,1381,1384,1387,1390,1393,1395,1398,1401],{"class":37,"line":343},[35,1375,1352],{"class":180},[35,1377,1378],{"class":237},"8",[35,1380,247],{"class":180},[35,1382,1383],{"class":45},"\"below\"",[35,1385,1386],{"class":176}," if",[35,1388,1389],{"class":180}," value ",[35,1391,1392],{"class":176},"\u003C",[35,1394,1294],{"class":180},[35,1396,1397],{"class":176},"else",[35,1399,1400],{"class":45}," \"above\"",[35,1402,717],{"class":180},[35,1404,1405,1407,1409],{"class":37,"line":354},[35,1406,1199],{"class":180},[35,1408,1202],{"class":45},[35,1410,717],{"class":180},[35,1412,1413,1415],{"class":37,"line":365},[35,1414,662],{"class":237},[35,1416,1417],{"class":180},"(outside)\n",[25,1419,1421],{"className":27,"code":1420,"language":29,"meta":30,"style":30},"soffice --headless --convert-to pdf on_time_delivery.xlsx > \u002Fdev\u002Fnull 2>&1\npdftoppm -r 70 -f 1 -l 1 -png on_time_delivery.pdf preview && ls preview*\n",[32,1422,1423,1449],{"__ignoreMap":30},[35,1424,1425,1428,1431,1434,1437,1440,1443,1446],{"class":37,"line":38},[35,1426,1427],{"class":41},"soffice",[35,1429,1430],{"class":237}," --headless",[35,1432,1433],{"class":237}," --convert-to",[35,1435,1436],{"class":45}," pdf",[35,1438,1439],{"class":45}," on_time_delivery.xlsx",[35,1441,1442],{"class":176}," >",[35,1444,1445],{"class":45}," \u002Fdev\u002Fnull",[35,1447,1448],{"class":176}," 2>&1\n",[35,1450,1451,1454,1457,1460,1463,1466,1469,1471,1474,1477,1480,1483,1486,1488],{"class":37,"line":190},[35,1452,1453],{"class":41},"pdftoppm",[35,1455,1456],{"class":237}," -r",[35,1458,1459],{"class":237}," 70",[35,1461,1462],{"class":237}," -f",[35,1464,1465],{"class":237}," 1",[35,1467,1468],{"class":237}," -l",[35,1470,1465],{"class":237},[35,1472,1473],{"class":237}," -png",[35,1475,1476],{"class":45}," on_time_delivery.pdf",[35,1478,1479],{"class":45}," preview",[35,1481,1482],{"class":180}," && ",[35,1484,1485],{"class":41},"ls",[35,1487,1479],{"class":45},[35,1489,1490],{"class":237},"*\n",[10,1492,1493],{},"A short table of the weeks outside the band, placed next to the chart, turns the picture into an action list: four weeks below the range — W32, W35, W36 and W41 — and one above it, W39 at 98.6%. Being above an upper limit is not always bad news, but a band with an upper limit says it is worth a look; very high on-time figures can mean deliveries are being promised too late. Rendering the workbook to an image with LibreOffice confirms the construction: the pale band sits between 95% and 98%, the lower helper is invisible, and the line passes in and out of the band where the table says it does.",[69,1495,78,1501,78,1504,78,1507,78,1512,78,1518,78,1522,78,1528,78,1532,78,1536,78,1540,78,1544,78,1548,78,1551,78,1554,78,1558,78,1560,78,1562,78,1564,78,1567,78,1570,78,1573,78,1575,78,1579,78,1581,78,1584,78,1586,78,1589,78,1592,78,1595,78,1597,78,1600,78,1602,78,1605,78,1607,78,1609,78,1612,78,1615,78,1617,78,1620,78,1624,78,1628,78,1630,78,1632,78,1635,78,1638,78,1640,78,1643,78,1645,78,1648,78,1652,78,1656,78,1659,78,1662,78,1664,78,1667,78,1669,78,1672,78,1674],{"viewBox":1496,"role":72,"ariaLabelledBy":1497,"xmlns":76,"style":1500},"245 0 269 224",[1498,1499],"band-outside-t","band-outside-d","width:100%;max-width:269px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[80,1502,1503],{"id":1498},"Weeks outside the 95–98% band",[84,1505,1506],{"id":1499},"Four weeks fall below the band — W32 at 94.1 percent, W35 at 93.2, W36 at 91.5 and W41 at 94.8 — and one, W39 at 98.6 percent, is above it.",[88,1508],{"x":1509,"y":91,"width":1510,"height":1511,"fill":94},"245","269","224",[88,1513],{"x":1514,"y":98,"width":1515,"height":1516,"rx":91,"fill":150,"stroke":151,"style":1517},"261.5","32.0","24.0","stroke-width:1px",[88,1519],{"x":1520,"y":98,"width":1521,"height":1516,"rx":91,"fill":150,"stroke":151,"style":1517},"293.5","59.4",[106,1523,1527],{"x":1524,"y":1525,"style":1526},"323.2","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[88,1529],{"x":1530,"y":98,"width":1531,"height":1516,"rx":91,"fill":150,"stroke":151,"style":1517},"352.9","71.6",[106,1533,1535],{"x":1534,"y":1525,"style":1526},"388.7","B",[88,1537],{"x":1538,"y":98,"width":1539,"height":1516,"rx":91,"fill":150,"stroke":151,"style":1517},"424.5","74.0",[106,1541,1543],{"x":1542,"y":1525,"style":1526},"461.5","C",[88,1545],{"x":1514,"y":1546,"width":1515,"height":1547,"rx":91,"fill":150,"stroke":151,"style":1517},"40.0","28.0",[106,1549,570],{"x":1550,"y":100,"style":1526},"277.5",[88,1552],{"x":1520,"y":1546,"width":1521,"height":1547,"rx":91,"fill":102,"stroke":103,"style":1553},"stroke-width:1.5px",[106,1555,1557],{"x":1524,"y":100,"style":1556},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Week",[88,1559],{"x":1530,"y":1546,"width":1531,"height":1547,"rx":91,"fill":102,"stroke":103,"style":1553},[106,1561,1227],{"x":1534,"y":100,"style":1556},[88,1563],{"x":1538,"y":1546,"width":1539,"height":1547,"rx":91,"fill":102,"stroke":103,"style":1553},[106,1565,1566],{"x":1542,"y":100,"style":1556},"Position",[88,1568],{"x":1514,"y":1569,"width":1515,"height":1547,"rx":91,"fill":150,"stroke":151,"style":1517},"68.0",[106,1571,558],{"x":1550,"y":1572,"style":1526},"87.0",[88,1574],{"x":1520,"y":1569,"width":1521,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1576,1578],{"x":1524,"y":1572,"style":1577},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","W32",[88,1580],{"x":1530,"y":1569,"width":1531,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1582,1583],{"x":1534,"y":1572,"style":1577},"94.1%",[88,1585],{"x":1538,"y":1569,"width":1539,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1587,1588],{"x":1542,"y":1572,"style":1577},"below",[88,1590],{"x":1514,"y":1591,"width":1515,"height":1547,"rx":91,"fill":150,"stroke":151,"style":1517},"96.0",[106,1593,605],{"x":1550,"y":1594,"style":1526},"115.0",[88,1596],{"x":1520,"y":1591,"width":1521,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1598,1599],{"x":1524,"y":1594,"style":1577},"W35",[88,1601],{"x":1530,"y":1591,"width":1531,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1603,1604],{"x":1534,"y":1594,"style":1577},"93.2%",[88,1606],{"x":1538,"y":1591,"width":1539,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1608,1588],{"x":1542,"y":1594,"style":1577},[88,1610],{"x":1514,"y":1611,"width":1515,"height":1547,"rx":91,"fill":150,"stroke":151,"style":1517},"124.0",[106,1613,625],{"x":1550,"y":1614,"style":1526},"143.0",[88,1616],{"x":1520,"y":1611,"width":1521,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1618,1619],{"x":1524,"y":1614,"style":1577},"W36",[88,1621],{"x":1530,"y":1611,"width":1531,"height":1547,"rx":91,"fill":1622,"stroke":1623,"style":1553},"#fce9e9","var(--danger,#dc2626)",[106,1625,1627],{"x":1534,"y":1614,"style":1626},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","91.5%",[88,1629],{"x":1538,"y":1611,"width":1539,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1631,1588],{"x":1542,"y":1614,"style":1577},[88,1633],{"x":1514,"y":1634,"width":1515,"height":1547,"rx":91,"fill":150,"stroke":151,"style":1517},"152.0",[106,1636,1345],{"x":1550,"y":1637,"style":1526},"171.0",[88,1639],{"x":1520,"y":1634,"width":1521,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1641,1642],{"x":1524,"y":1637,"style":1577},"W39",[88,1644],{"x":1530,"y":1634,"width":1531,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1646,1647],{"x":1534,"y":1637,"style":1577},"98.6%",[88,1649],{"x":1538,"y":1634,"width":1539,"height":1547,"rx":91,"fill":1650,"stroke":1651,"style":1553},"#fdefd8","var(--gold,#b4740a)",[106,1653,1655],{"x":1542,"y":1637,"style":1654},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","above",[88,1657],{"x":1514,"y":1658,"width":1515,"height":1547,"rx":91,"fill":150,"stroke":151,"style":1517},"180.0",[106,1660,1355],{"x":1550,"y":1661,"style":1526},"199.0",[88,1663],{"x":1520,"y":1658,"width":1521,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1665,1666],{"x":1524,"y":1661,"style":1577},"W41",[88,1668],{"x":1530,"y":1658,"width":1531,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1670,1671],{"x":1534,"y":1661,"style":1577},"94.8%",[88,1673],{"x":1538,"y":1658,"width":1539,"height":1547,"rx":91,"fill":94,"stroke":151,"style":1517},[106,1675,1588],{"x":1542,"y":1661,"style":1577},[20,1677,1679],{"id":1678},"bands-single-targets-and-tolerances","Bands, single targets and tolerances",[10,1681,1682,1683,18],{},"A band is the right picture when there is a genuine range of acceptable values. When there is a single target with a tolerance — 95% plus or minus one point — the same construction works with the limits set to target minus and plus the tolerance. When there is a single target and no tolerance, a horizontal target line is cleaner than a band, and when the question is \"how far from target is each week?\", a variance chart or conditional formatting on the table says more than a band. The cell-based version of the same idea, colouring values outside a tolerance, is covered in ",[14,1684,1686],{"href":1685},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-values-outside-a-tolerance-of-target-with-openpyxl\u002F","Highlight Values Outside a Tolerance of Target with openpyxl",[69,1688,78,1694,78,1697,78,1700,78,1704,78,1710,78,1715,78,1720,78,1724,78,1728,78,1732,78,1736,78,1739,78,1744,78,1748,78,1751,78,1754,78,1757],{"viewBox":1689,"role":72,"ariaLabelledBy":1690,"xmlns":76,"style":1693},"4 0 752 199",[1691,1692],"band-vs-line-t","band-vs-line-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[80,1695,1696],{"id":1691},"Target band or target line",[84,1698,1699],{"id":1692},"A target band suits a range of acceptable values and shows at a glance when the measure leaves it. A target line suits a single value to hit and shows the distance above or below it.",[88,1701],{"x":625,"y":91,"width":1702,"height":1703,"fill":94},"752","199",[88,1705],{"x":1706,"y":98,"width":1707,"height":1708,"rx":1709,"fill":129,"stroke":130,"style":104},"20.0","351.0","167.0","14",[106,1711,1224],{"x":1712,"y":1713,"style":1714},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[37,1716],{"x1":1717,"y1":1718,"x2":1719,"y2":1718,"stroke":130,"style":1517},"36.0","57.0","355.0",[106,1721,1723],{"x":1712,"y":1722,"style":1577},"77.0","acceptable range, two limits",[106,1725,1727],{"x":1712,"y":1726,"style":1577},"103.0","stacked area behind the line",[106,1729,1731],{"x":1712,"y":1730,"style":1577},"129.0","points outside stand out",[106,1733,1735],{"x":1712,"y":1734,"style":1577},"155.0","e.g. 95–98% on time",[88,1737],{"x":1738,"y":98,"width":1707,"height":1708,"rx":1709,"fill":102,"stroke":103,"style":104},"389.0",[106,1740,1743],{"x":1741,"y":1713,"style":1742},"564.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Target line",[37,1745],{"x1":1746,"y1":1718,"x2":1747,"y2":1718,"stroke":103,"style":1517},"405.0","724.0",[106,1749,1750],{"x":1741,"y":1722,"style":1577},"one value to reach",[106,1752,1753],{"x":1741,"y":1726,"style":1577},"extra line series",[106,1755,1756],{"x":1741,"y":1730,"style":1577},"shows the gap to target",[106,1758,1759],{"x":1741,"y":1734,"style":1577},"e.g. revenue target",[20,1761,1763],{"id":1762},"common-pitfalls","Common pitfalls",[1765,1766,1767,1775,1784,1790,1796],"ul",{},[1768,1769,1770,1774],"li",{},[1771,1772,1773],"strong",{},"A visible lower series."," Switch off both its fill and its outline.",[1768,1776,1777,1780,1781,18],{},[1771,1778,1779],{},"Clustered instead of stacked."," The band only sits on the lower limit when ",[32,1782,1783],{},"grouping = \"stacked\"",[1768,1785,1786,1789],{},[1771,1787,1788],{},"Line drawn under the band."," Add the line chart to the area chart, not the other way round.",[1768,1791,1792,1795],{},[1771,1793,1794],{},"Band limits typed into helper rows."," Drive them from two input cells with formulas.",[1768,1797,1798,1801],{},[1771,1799,1800],{},"A 0–100% axis."," Crop the value axis so movements around the band are visible.",[20,1803,1805],{"id":1804},"conclusion","Conclusion",[10,1807,1808,1809,1811],{},"To shade a target band behind a line in Excel with openpyxl, add a stacked area chart whose first series is the lower limit with no fill and whose second is the band width in a light colour, then add a line chart of the actual values to it with ",[32,1810,1048],{}," so both share the axes. Drive the limits from two input cells, remove the helper from the legend, crop the value axis, and list the periods outside the band next to the chart.",[20,1813,1815],{"id":1814},"frequently-asked-questions","Frequently asked questions",[10,1817,1818,1821],{},[1771,1819,1820],{},"How do I add a shaded range to a line chart in Excel?","\nCombine a stacked area chart — an invisible series at the lower limit and a coloured series of the band width — with the line chart on the same axes.",[10,1823,1824,1827,1828,1831],{},[1771,1825,1826],{},"How do I combine an area chart and a line chart in openpyxl?","\nBuild both charts, then add the line chart to the area chart with ",[32,1829,1830],{},"area_chart += line_chart"," before adding it to the sheet.",[10,1833,1834,1837,1838,1841],{},[1771,1835,1836],{},"How do I hide a helper series from the legend?","\nSet ",[32,1839,1840],{},"chart.legend.legendEntry = [LegendEntry(idx=0, delete=True)]",", using the helper series' index.",[10,1843,1844,1847],{},[1771,1845,1846],{},"Can the band change partway through the period?","\nYes; give the helper columns different values on different rows and the band steps where the target changes.",[20,1849,1851],{"id":1850},"related","Related",[1765,1853,1854,1860,1867,1874],{},[1768,1855,1856,1857,1859],{},"Up one level: ",[14,1858,17],{"href":16}," — native Excel charts from Python.",[1768,1861,1862,1866],{},[14,1863,1865],{"href":1864},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-line-chart-to-excel-report-with-python\u002F","Add a Line Chart to an Excel Report with Python"," — line chart basics.",[1768,1868,1869,1873],{},[14,1870,1872],{"href":1871},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-an-area-chart-with-openpyxl\u002F","Create an Area Chart with openpyxl"," — stacked areas.",[1768,1875,1876,1880],{},[14,1877,1879],{"href":1878},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-monthly-kpi-scorecard-with-targets\u002F","Build a Monthly KPI Scorecard with Targets"," — targets in a report.",[1882,1883,1884],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":30,"searchDepth":190,"depth":190,"links":1886},[1887,1888,1889,1890,1891,1892,1893,1894,1895,1896,1897],{"id":22,"depth":190,"text":23},{"id":58,"depth":190,"text":59},{"id":163,"depth":190,"text":164},{"id":731,"depth":190,"text":732},{"id":1063,"depth":190,"text":1064},{"id":1234,"depth":190,"text":1235},{"id":1678,"depth":190,"text":1679},{"id":1762,"depth":190,"text":1763},{"id":1804,"depth":190,"text":1805},{"id":1814,"depth":190,"text":1815},{"id":1850,"depth":190,"text":1851},"2026-10-02","Show a target range — on-time delivery between 95% and 98%, stock cover between two and four weeks — as a shaded band behind a line in a native Excel chart built with openpyxl: a stacked area chart with an invisible lower series and a coloured band series, a line chart combined on the same axes, band limits driven by two input cells, the helper series removed from the legend, and the weeks outside the band listed next to the chart.","md",[1902,1904,1906,1908],{"q":1820,"a":1903},"Combine a stacked area chart — an invisible series at the lower limit and a coloured series of the band width — with the line chart on the same axes.",{"q":1826,"a":1905},"Build both charts, then add the line chart to the area chart with area_chart += line_chart before adding it to the sheet.",{"q":1836,"a":1907},"Set chart.legend.legendEntry = [LegendEntry(idx=0, delete=True)], using the helper series' index.",{"q":1846,"a":1909},"Yes; give the helper columns different values on different rows and the band steps where the target changes.","target band line chart excel python",null,{"breadcrumb":1913},[1914,1916,1919],{"name":1915,"item":586},"Home",{"name":1917,"item":1918},"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\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshade-a-target-band-behind-a-line-chart",{"title":1922,"description":1923},"Shade a Target Band Behind a Line Chart in Excel with Python","A target band behind a line chart with openpyxl: stacked area with an invisible base and a coloured band, combined with a line chart, band limits from input cells, legend cleanup and out-of-band weeks.","shade-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",[1927,1928],"charts-and-images","openpyxl","how-to","HSwiXvivfupUydJ4ff5PoMzIfsnDJ3qTlykH9hkSYqk",[1932,1936],{"title":1933,"path":1934,"stem":1935,"children":-1},"Put a Chart on Its Own Chartsheet with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fput-a-chart-on-its-own-chartsheet-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fput-a-chart-on-its-own-chartsheet-with-openpyxl\u002Findex",{"title":1937,"path":1938,"stem":1939,"children":-1},"Show Negative Bars in a Different Colour","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fshow-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",[1941,1943],{"path":1942,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":1944,"title":1917},"\u002Fformatting-and-charting-excel-reports-with-python",[],[1947,1950,1953,1956,1959,1962],{"path":1948,"title":1949,"kind":1929},"\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":1951,"title":1952,"kind":1929},"\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":1954,"title":1955,"kind":1929},"\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":1957,"title":1958,"kind":1929},"\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":1960,"title":1961,"kind":1929},"\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":1963,"title":1964,"kind":1929},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python","Build a Waterfall Chart in Excel with Python",1790972113342]