[{"data":1,"prerenderedAt":2836},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard":2801,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard":2810,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard":2815,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard":2816},{"id":4,"title":5,"body":6,"dateModified":2768,"datePublished":2768,"description":2769,"extension":2770,"faq":2771,"keyword":2780,"kind":2781,"meta":2782,"navigation":148,"path":2790,"seo":2791,"slug":2793,"stem":2794,"topics":2795,"type":2799,"__hash__":2800},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard\u002Findex.md","Build a Rolling 12-Month Chart on an Excel Dashboard",{"type":7,"value":8,"toc":2756},"minimark",[9,29,34,65,76,80,571,574,578,581,1114,1151,1338,1342,1353,1712,1727,1933,1937,1940,2514,2524,2598,2602,2619,2623,2672,2676,2691,2695,2701,2707,2713,2719,2723,2752],[10,11,12,13,17,18,22,23,28],"p",{},"A trend chart on a dashboard usually needs to show the ",[14,15,16],"em",{},"latest"," twelve months — not January to December of a fixed year, and not every month since records began. Done by hand, someone edits the chart's source range every month; done carelessly in code, the script regenerates the chart with hard-coded ranges, and the workbook breaks as soon as someone pastes in another month of data. A sturdier design puts the moving window into formulas: a small helper block works out which twelve months to show from the newest date in the data, totals each one with ",[19,20,21],"code",{},"SUMIFS",", and the chart points at that block, which never moves. This page builds that with openpyxl, adds an override cell so readers can look back to an earlier period, plots the same months a year earlier for comparison, and checks with LibreOffice that the window follows new data without the chart being touched. It belongs to ",[24,25,27],"a",{"href":26},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002F","Building Multi-Sheet Excel Dashboards",".",[30,31,33],"h2",{"id":32},"prerequisites","Prerequisites",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas numpy\n","bash","",[19,42,43],{"__ignoreMap":40},[44,45,48,52,56,59,62],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," openpyxl",[44,60,61],{"class":54}," pandas",[44,63,64],{"class":54}," numpy\n",[10,66,67,68,71,72,28],{},"LibreOffice (",[19,69,70],{},"soffice",") is used only to recalculate the saved workbook in the checks, because openpyxl writes formulas but does not calculate them. The data is thirty months of revenue, one row per month, held in an Excel table so formulas can refer to its columns by name — the same structure as in ",[24,73,75],{"href":74},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flink-dashboard-cells-to-data-sheets-with-formulas\u002F","Link Dashboard Cells to Data Sheets with Formulas",[30,77,79],{"id":78},"step-1-the-data-sheet","Step 1 — The data sheet",[35,81,85],{"className":82,"code":83,"language":84,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","import numpy as np\nimport pandas as pd\nfrom openpyxl import Workbook\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\n\ndef sales_data(months):\n    idx = pd.date_range(\"2024-01-01\", periods=months, freq=\"MS\")\n    t = np.arange(months)\n    rng = np.random.default_rng(7)\n    revenue = (40000 + 900 * t + 6000 * np.sin(t \u002F 12 * 2 * np.pi) + rng.normal(0, 1500, months)).round(-2)\n    return pd.DataFrame({\"Month\": idx, \"Revenue\": revenue})\n\ndef data_sheet(wb, df):\n    ws = wb.create_sheet(\"Data\")\n    ws.append([\"Month\", \"Revenue\"])\n    for m, r in df.itertuples(index=False):\n        ws.append([m.to_pydatetime(), float(r)])\n        ws.cell(ws.max_row, 1).number_format = \"mmm yyyy\"\n    table = Table(displayName=\"Sales\", ref=f\"A1:B{ws.max_row}\")\n    table.tableStyleInfo = TableStyleInfo(name=\"TableStyleLight9\", showRowStripes=True)\n    ws.add_table(table)\n    return ws\n\nsales = sales_data(30)\nprint(sales.iloc[[0, -1]].to_string(index=False))\n","python",[19,86,87,103,116,130,143,150,163,201,212,229,306,327,332,343,359,374,400,412,429,476,507,513,521,526,542],{"__ignoreMap":40},[44,88,89,93,97,100],{"class":46,"line":47},[44,90,92],{"class":91},"s-kum","import",[44,94,96],{"class":95},"skGVy"," numpy ",[44,98,99],{"class":91},"as",[44,101,102],{"class":95}," np\n",[44,104,106,108,111,113],{"class":46,"line":105},2,[44,107,92],{"class":91},[44,109,110],{"class":95}," pandas ",[44,112,99],{"class":91},[44,114,115],{"class":95}," pd\n",[44,117,119,122,125,127],{"class":46,"line":118},3,[44,120,121],{"class":91},"from",[44,123,124],{"class":95}," openpyxl ",[44,126,92],{"class":91},[44,128,129],{"class":95}," Workbook\n",[44,131,133,135,138,140],{"class":46,"line":132},4,[44,134,121],{"class":91},[44,136,137],{"class":95}," openpyxl.worksheet.table ",[44,139,92],{"class":91},[44,141,142],{"class":95}," Table, TableStyleInfo\n",[44,144,146],{"class":46,"line":145},5,[44,147,149],{"emptyLinePlaceholder":148},true,"\n",[44,151,153,156,160],{"class":46,"line":152},6,[44,154,155],{"class":91},"def",[44,157,159],{"class":158},"s_Opv"," sales_data",[44,161,162],{"class":95},"(months):\n",[44,164,166,169,172,175,178,181,185,187,190,193,195,198],{"class":46,"line":165},7,[44,167,168],{"class":95},"    idx ",[44,170,171],{"class":91},"=",[44,173,174],{"class":95}," pd.date_range(",[44,176,177],{"class":54},"\"2024-01-01\"",[44,179,180],{"class":95},", ",[44,182,184],{"class":183},"sa561","periods",[44,186,171],{"class":91},[44,188,189],{"class":95},"months, ",[44,191,192],{"class":183},"freq",[44,194,171],{"class":91},[44,196,197],{"class":54},"\"MS\"",[44,199,200],{"class":95},")\n",[44,202,204,207,209],{"class":46,"line":203},8,[44,205,206],{"class":95},"    t ",[44,208,171],{"class":91},[44,210,211],{"class":95}," np.arange(months)\n",[44,213,215,218,220,223,227],{"class":46,"line":214},9,[44,216,217],{"class":95},"    rng ",[44,219,171],{"class":91},[44,221,222],{"class":95}," np.random.default_rng(",[44,224,226],{"class":225},"sP0c6","7",[44,228,200],{"class":95},[44,230,232,235,237,240,243,246,249,252,255,258,261,263,266,269,272,274,277,279,282,284,287,290,292,295,298,301,304],{"class":46,"line":231},10,[44,233,234],{"class":95},"    revenue ",[44,236,171],{"class":91},[44,238,239],{"class":95}," (",[44,241,242],{"class":225},"40000",[44,244,245],{"class":91}," +",[44,247,248],{"class":225}," 900",[44,250,251],{"class":91}," *",[44,253,254],{"class":95}," t ",[44,256,257],{"class":91},"+",[44,259,260],{"class":225}," 6000",[44,262,251],{"class":91},[44,264,265],{"class":95}," np.sin(t ",[44,267,268],{"class":91},"\u002F",[44,270,271],{"class":225}," 12",[44,273,251],{"class":91},[44,275,276],{"class":225}," 2",[44,278,251],{"class":91},[44,280,281],{"class":95}," np.pi) ",[44,283,257],{"class":91},[44,285,286],{"class":95}," rng.normal(",[44,288,289],{"class":225},"0",[44,291,180],{"class":95},[44,293,294],{"class":225},"1500",[44,296,297],{"class":95},", months)).round(",[44,299,300],{"class":91},"-",[44,302,303],{"class":225},"2",[44,305,200],{"class":95},[44,307,309,312,315,318,321,324],{"class":46,"line":308},11,[44,310,311],{"class":91},"    return",[44,313,314],{"class":95}," pd.DataFrame({",[44,316,317],{"class":54},"\"Month\"",[44,319,320],{"class":95},": idx, ",[44,322,323],{"class":54},"\"Revenue\"",[44,325,326],{"class":95},": revenue})\n",[44,328,330],{"class":46,"line":329},12,[44,331,149],{"emptyLinePlaceholder":148},[44,333,335,337,340],{"class":46,"line":334},13,[44,336,155],{"class":91},[44,338,339],{"class":158}," data_sheet",[44,341,342],{"class":95},"(wb, df):\n",[44,344,346,349,351,354,357],{"class":46,"line":345},14,[44,347,348],{"class":95},"    ws ",[44,350,171],{"class":91},[44,352,353],{"class":95}," wb.create_sheet(",[44,355,356],{"class":54},"\"Data\"",[44,358,200],{"class":95},[44,360,362,365,367,369,371],{"class":46,"line":361},15,[44,363,364],{"class":95},"    ws.append([",[44,366,317],{"class":54},[44,368,180],{"class":95},[44,370,323],{"class":54},[44,372,373],{"class":95},"])\n",[44,375,377,380,383,386,389,392,394,397],{"class":46,"line":376},16,[44,378,379],{"class":91},"    for",[44,381,382],{"class":95}," m, r ",[44,384,385],{"class":91},"in",[44,387,388],{"class":95}," df.itertuples(",[44,390,391],{"class":183},"index",[44,393,171],{"class":91},[44,395,396],{"class":225},"False",[44,398,399],{"class":95},"):\n",[44,401,403,406,409],{"class":46,"line":402},17,[44,404,405],{"class":95},"        ws.append([m.to_pydatetime(), ",[44,407,408],{"class":225},"float",[44,410,411],{"class":95},"(r)])\n",[44,413,415,418,421,424,426],{"class":46,"line":414},18,[44,416,417],{"class":95},"        ws.cell(ws.max_row, ",[44,419,420],{"class":225},"1",[44,422,423],{"class":95},").number_format ",[44,425,171],{"class":91},[44,427,428],{"class":54}," \"mmm yyyy\"\n",[44,430,432,435,437,440,443,445,448,450,453,455,458,461,465,468,471,474],{"class":46,"line":431},19,[44,433,434],{"class":95},"    table ",[44,436,171],{"class":91},[44,438,439],{"class":95}," Table(",[44,441,442],{"class":183},"displayName",[44,444,171],{"class":91},[44,446,447],{"class":54},"\"Sales\"",[44,449,180],{"class":95},[44,451,452],{"class":183},"ref",[44,454,171],{"class":91},[44,456,457],{"class":91},"f",[44,459,460],{"class":54},"\"A1:B",[44,462,464],{"class":463},"sSjpA","{",[44,466,467],{"class":95},"ws.max_row",[44,469,470],{"class":463},"}",[44,472,473],{"class":54},"\"",[44,475,200],{"class":95},[44,477,479,482,484,487,490,492,495,497,500,502,505],{"class":46,"line":478},20,[44,480,481],{"class":95},"    table.tableStyleInfo ",[44,483,171],{"class":91},[44,485,486],{"class":95}," TableStyleInfo(",[44,488,489],{"class":183},"name",[44,491,171],{"class":91},[44,493,494],{"class":54},"\"TableStyleLight9\"",[44,496,180],{"class":95},[44,498,499],{"class":183},"showRowStripes",[44,501,171],{"class":91},[44,503,504],{"class":225},"True",[44,506,200],{"class":95},[44,508,510],{"class":46,"line":509},21,[44,511,512],{"class":95},"    ws.add_table(table)\n",[44,514,516,518],{"class":46,"line":515},22,[44,517,311],{"class":91},[44,519,520],{"class":95}," ws\n",[44,522,524],{"class":46,"line":523},23,[44,525,149],{"emptyLinePlaceholder":148},[44,527,529,532,534,537,540],{"class":46,"line":528},24,[44,530,531],{"class":95},"sales ",[44,533,171],{"class":91},[44,535,536],{"class":95}," sales_data(",[44,538,539],{"class":225},"30",[44,541,200],{"class":95},[44,543,545,548,551,553,555,557,559,562,564,566,568],{"class":46,"line":544},25,[44,546,547],{"class":225},"print",[44,549,550],{"class":95},"(sales.iloc[[",[44,552,289],{"class":225},[44,554,180],{"class":95},[44,556,300],{"class":91},[44,558,420],{"class":225},[44,560,561],{"class":95},"]].to_string(",[44,563,391],{"class":183},[44,565,171],{"class":91},[44,567,396],{"class":225},[44,569,570],{"class":95},"))\n",[10,572,573],{},"Thirty months from January 2024 to June 2026, with growth, a seasonal swing and noise. Storing the months as real dates on the first of the month — not as text like \"Jun 2026\" — is what lets the formulas below do date arithmetic on them. In a real report this sheet is written from your source data each run, or appended to by people; either way the dashboard below adapts.",[30,575,577],{"id":576},"step-2-a-helper-block-that-works-out-the-window","Step 2 — A helper block that works out the window",[10,579,580],{},"The dashboard has an end-month cell, an optional override, and twelve rows of formulas. Everything the chart shows comes from those rows:",[35,582,584],{"className":82,"code":583,"language":84,"meta":40,"style":40},"from openpyxl.worksheet.datavalidation import DataValidation\nfrom openpyxl.styles import Font\n\ndef dashboard(wb, df):\n    ws = wb.active\n    ws.title = \"Dashboard\"\n    ws[\"A1\"], ws[\"A1\"].font = \"Revenue — rolling 12 months\", Font(size=14, bold=True)\n    ws[\"A2\"], ws[\"A3\"] = \"Show 12 months ending\", \"Override (optional)\"\n    ws[\"B2\"] = '=IF(B3=\"\",MAX(Sales[Month]),B3)'\n    for c in (\"B2\", \"B3\"):\n        ws[c].number_format = \"mmm yyyy\"\n    dv = DataValidation(type=\"list\", formula1=\"=Data!$A$2:$A$500\", allow_blank=True)\n    ws.add_data_validation(dv)\n    dv.add(\"B3\")\n    ws.append([])\n    ws[\"A5\"], ws[\"B5\"], ws[\"C5\"] = \"Month\", \"Revenue\", \"Same month last year\"\n    for i in range(12):\n        r = 6 + i\n        ws[f\"A{r}\"] = f\"=EDATE($B$2,{i - 11})\"\n        ws[f\"B{r}\"] = f\"=SUMIFS(Sales[Revenue],Sales[Month],A{r})\"\n        ws[f\"C{r}\"] = f'=IF(COUNTIFS(Sales[Month],EDATE(A{r},-12))=0,NA(),SUMIFS(Sales[Revenue],Sales[Month],EDATE(A{r},-12)))'\n        ws[f\"A{r}\"].number_format = \"mmm yy\"\n        ws[f\"B{r}\"].number_format = ws[f\"C{r}\"].number_format = \"#,##0\"\n    ws.column_dimensions[\"A\"].width = 22\n    ws.column_dimensions[\"B\"].width = ws.column_dimensions[\"C\"].width = 20\n    return ws\n",[19,585,586,598,610,614,623,632,642,685,710,724,744,753,792,797,806,811,844,864,879,923,957,1001,1025,1067,1083,1107],{"__ignoreMap":40},[44,587,588,590,593,595],{"class":46,"line":47},[44,589,121],{"class":91},[44,591,592],{"class":95}," openpyxl.worksheet.datavalidation ",[44,594,92],{"class":91},[44,596,597],{"class":95}," DataValidation\n",[44,599,600,602,605,607],{"class":46,"line":105},[44,601,121],{"class":91},[44,603,604],{"class":95}," openpyxl.styles ",[44,606,92],{"class":91},[44,608,609],{"class":95}," Font\n",[44,611,612],{"class":46,"line":118},[44,613,149],{"emptyLinePlaceholder":148},[44,615,616,618,621],{"class":46,"line":132},[44,617,155],{"class":91},[44,619,620],{"class":158}," dashboard",[44,622,342],{"class":95},[44,624,625,627,629],{"class":46,"line":145},[44,626,348],{"class":95},[44,628,171],{"class":91},[44,630,631],{"class":95}," wb.active\n",[44,633,634,637,639],{"class":46,"line":152},[44,635,636],{"class":95},"    ws.title ",[44,638,171],{"class":91},[44,640,641],{"class":54}," \"Dashboard\"\n",[44,643,644,647,650,653,655,658,660,663,666,669,671,674,676,679,681,683],{"class":46,"line":165},[44,645,646],{"class":95},"    ws[",[44,648,649],{"class":54},"\"A1\"",[44,651,652],{"class":95},"], ws[",[44,654,649],{"class":54},[44,656,657],{"class":95},"].font ",[44,659,171],{"class":91},[44,661,662],{"class":54}," \"Revenue — rolling 12 months\"",[44,664,665],{"class":95},", Font(",[44,667,668],{"class":183},"size",[44,670,171],{"class":91},[44,672,673],{"class":225},"14",[44,675,180],{"class":95},[44,677,678],{"class":183},"bold",[44,680,171],{"class":91},[44,682,504],{"class":225},[44,684,200],{"class":95},[44,686,687,689,692,694,697,700,702,705,707],{"class":46,"line":203},[44,688,646],{"class":95},[44,690,691],{"class":54},"\"A2\"",[44,693,652],{"class":95},[44,695,696],{"class":54},"\"A3\"",[44,698,699],{"class":95},"] ",[44,701,171],{"class":91},[44,703,704],{"class":54}," \"Show 12 months ending\"",[44,706,180],{"class":95},[44,708,709],{"class":54},"\"Override (optional)\"\n",[44,711,712,714,717,719,721],{"class":46,"line":214},[44,713,646],{"class":95},[44,715,716],{"class":54},"\"B2\"",[44,718,699],{"class":95},[44,720,171],{"class":91},[44,722,723],{"class":54}," '=IF(B3=\"\",MAX(Sales[Month]),B3)'\n",[44,725,726,728,731,733,735,737,739,742],{"class":46,"line":231},[44,727,379],{"class":91},[44,729,730],{"class":95}," c ",[44,732,385],{"class":91},[44,734,239],{"class":95},[44,736,716],{"class":54},[44,738,180],{"class":95},[44,740,741],{"class":54},"\"B3\"",[44,743,399],{"class":95},[44,745,746,749,751],{"class":46,"line":308},[44,747,748],{"class":95},"        ws[c].number_format ",[44,750,171],{"class":91},[44,752,428],{"class":54},[44,754,755,758,760,763,766,768,771,773,776,778,781,783,786,788,790],{"class":46,"line":329},[44,756,757],{"class":95},"    dv ",[44,759,171],{"class":91},[44,761,762],{"class":95}," DataValidation(",[44,764,765],{"class":183},"type",[44,767,171],{"class":91},[44,769,770],{"class":54},"\"list\"",[44,772,180],{"class":95},[44,774,775],{"class":183},"formula1",[44,777,171],{"class":91},[44,779,780],{"class":54},"\"=Data!$A$2:$A$500\"",[44,782,180],{"class":95},[44,784,785],{"class":183},"allow_blank",[44,787,171],{"class":91},[44,789,504],{"class":225},[44,791,200],{"class":95},[44,793,794],{"class":46,"line":334},[44,795,796],{"class":95},"    ws.add_data_validation(dv)\n",[44,798,799,802,804],{"class":46,"line":345},[44,800,801],{"class":95},"    dv.add(",[44,803,741],{"class":54},[44,805,200],{"class":95},[44,807,808],{"class":46,"line":361},[44,809,810],{"class":95},"    ws.append([])\n",[44,812,813,815,818,820,823,825,828,830,832,835,837,839,841],{"class":46,"line":376},[44,814,646],{"class":95},[44,816,817],{"class":54},"\"A5\"",[44,819,652],{"class":95},[44,821,822],{"class":54},"\"B5\"",[44,824,652],{"class":95},[44,826,827],{"class":54},"\"C5\"",[44,829,699],{"class":95},[44,831,171],{"class":91},[44,833,834],{"class":54}," \"Month\"",[44,836,180],{"class":95},[44,838,323],{"class":54},[44,840,180],{"class":95},[44,842,843],{"class":54},"\"Same month last year\"\n",[44,845,846,848,851,853,856,859,862],{"class":46,"line":402},[44,847,379],{"class":91},[44,849,850],{"class":95}," i ",[44,852,385],{"class":91},[44,854,855],{"class":225}," range",[44,857,858],{"class":95},"(",[44,860,861],{"class":225},"12",[44,863,399],{"class":95},[44,865,866,869,871,874,876],{"class":46,"line":414},[44,867,868],{"class":95},"        r ",[44,870,171],{"class":91},[44,872,873],{"class":225}," 6",[44,875,245],{"class":91},[44,877,878],{"class":95}," i\n",[44,880,881,884,886,889,891,894,896,898,900,902,905,908,910,913,915,918,920],{"class":46,"line":431},[44,882,883],{"class":95},"        ws[",[44,885,457],{"class":91},[44,887,888],{"class":54},"\"A",[44,890,464],{"class":463},[44,892,893],{"class":95},"r",[44,895,470],{"class":463},[44,897,473],{"class":54},[44,899,699],{"class":95},[44,901,171],{"class":91},[44,903,904],{"class":91}," f",[44,906,907],{"class":54},"\"=EDATE($B$2,",[44,909,464],{"class":463},[44,911,912],{"class":95},"i ",[44,914,300],{"class":91},[44,916,917],{"class":225}," 11",[44,919,470],{"class":463},[44,921,922],{"class":54},")\"\n",[44,924,925,927,929,932,934,936,938,940,942,944,946,949,951,953,955],{"class":46,"line":478},[44,926,883],{"class":95},[44,928,457],{"class":91},[44,930,931],{"class":54},"\"B",[44,933,464],{"class":463},[44,935,893],{"class":95},[44,937,470],{"class":463},[44,939,473],{"class":54},[44,941,699],{"class":95},[44,943,171],{"class":91},[44,945,904],{"class":91},[44,947,948],{"class":54},"\"=SUMIFS(Sales[Revenue],Sales[Month],A",[44,950,464],{"class":463},[44,952,893],{"class":95},[44,954,470],{"class":463},[44,956,922],{"class":54},[44,958,959,961,963,966,968,970,972,974,976,978,980,983,985,987,989,992,994,996,998],{"class":46,"line":509},[44,960,883],{"class":95},[44,962,457],{"class":91},[44,964,965],{"class":54},"\"C",[44,967,464],{"class":463},[44,969,893],{"class":95},[44,971,470],{"class":463},[44,973,473],{"class":54},[44,975,699],{"class":95},[44,977,171],{"class":91},[44,979,904],{"class":91},[44,981,982],{"class":54},"'=IF(COUNTIFS(Sales[Month],EDATE(A",[44,984,464],{"class":463},[44,986,893],{"class":95},[44,988,470],{"class":463},[44,990,991],{"class":54},",-12))=0,NA(),SUMIFS(Sales[Revenue],Sales[Month],EDATE(A",[44,993,464],{"class":463},[44,995,893],{"class":95},[44,997,470],{"class":463},[44,999,1000],{"class":54},",-12)))'\n",[44,1002,1003,1005,1007,1009,1011,1013,1015,1017,1020,1022],{"class":46,"line":515},[44,1004,883],{"class":95},[44,1006,457],{"class":91},[44,1008,888],{"class":54},[44,1010,464],{"class":463},[44,1012,893],{"class":95},[44,1014,470],{"class":463},[44,1016,473],{"class":54},[44,1018,1019],{"class":95},"].number_format ",[44,1021,171],{"class":91},[44,1023,1024],{"class":54}," \"mmm yy\"\n",[44,1026,1027,1029,1031,1033,1035,1037,1039,1041,1043,1045,1048,1050,1052,1054,1056,1058,1060,1062,1064],{"class":46,"line":523},[44,1028,883],{"class":95},[44,1030,457],{"class":91},[44,1032,931],{"class":54},[44,1034,464],{"class":463},[44,1036,893],{"class":95},[44,1038,470],{"class":463},[44,1040,473],{"class":54},[44,1042,1019],{"class":95},[44,1044,171],{"class":91},[44,1046,1047],{"class":95}," ws[",[44,1049,457],{"class":91},[44,1051,965],{"class":54},[44,1053,464],{"class":463},[44,1055,893],{"class":95},[44,1057,470],{"class":463},[44,1059,473],{"class":54},[44,1061,1019],{"class":95},[44,1063,171],{"class":91},[44,1065,1066],{"class":54}," \"#,##0\"\n",[44,1068,1069,1072,1075,1078,1080],{"class":46,"line":528},[44,1070,1071],{"class":95},"    ws.column_dimensions[",[44,1073,1074],{"class":54},"\"A\"",[44,1076,1077],{"class":95},"].width ",[44,1079,171],{"class":91},[44,1081,1082],{"class":225}," 22\n",[44,1084,1085,1087,1090,1092,1094,1097,1100,1102,1104],{"class":46,"line":544},[44,1086,1071],{"class":95},[44,1088,1089],{"class":54},"\"B\"",[44,1091,1077],{"class":95},[44,1093,171],{"class":91},[44,1095,1096],{"class":95}," ws.column_dimensions[",[44,1098,1099],{"class":54},"\"C\"",[44,1101,1077],{"class":95},[44,1103,171],{"class":91},[44,1105,1106],{"class":225}," 20\n",[44,1108,1110,1112],{"class":46,"line":1109},26,[44,1111,311],{"class":91},[44,1113,520],{"class":95},[10,1115,1116,1119,1120,1123,1124,1127,1128,1131,1132,1134,1135,1138,1139,1142,1143,1145,1146,1150],{},[19,1117,1118],{},"B2"," is the anchor: the newest month in the data, unless the override cell ",[19,1121,1122],{},"B3"," holds a month, in which case that wins. The month rows count back from the anchor with ",[19,1125,1126],{},"EDATE",", which adds whole months and handles year boundaries — ",[19,1129,1130],{},"EDATE(June 2026, -11)"," is July 2025. Each month's revenue is a ",[19,1133,21],{}," on the table, so it picks up the matching row wherever it sits in the data. The prior-year column uses ",[19,1136,1137],{},"NA()"," where there is no data a year earlier: a chart skips ",[19,1140,1141],{},"#N\u002FA"," instead of drawing a misleading drop to zero, which is what a plain ",[19,1144,21],{}," returning 0 would show. The override is a dropdown restricted to the data's months, so a reader cannot type a date that does not exist in the data; ",[24,1147,1149],{"href":1148},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-slicer-style-filters-to-an-excel-dashboard\u002F","Add a Dropdown Filter That Drives an Excel Dashboard"," covers dropdowns in more detail.",[1152,1153,1161,1162,1161,1166,1161,1170,1161,1177,1161,1186,1161,1197,1161,1204,1161,1209,1161,1213,1161,1219,1161,1224,1161,1228,1161,1235,1161,1240,1161,1243,1161,1246,1161,1250,1161,1253,1161,1258,1161,1265,1161,1271,1161,1275,1161,1279,1161,1283,1161,1287,1161,1290,1161,1295,1161,1302,1161,1306,1161,1309,1161,1312,1161,1316,1161,1319,1161,1322,1161,1328,1161,1332,1161,1335],"svg",{"viewBox":1154,"role":1155,"ariaLabelledBy":1156,"xmlns":1159,"style":1160},"4 0 752 160","img",[1157,1158],"r12-flow-t","r12-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[1163,1164,1165],"title",{"id":1157},"How the window follows the data",[1167,1168,1169],"desc",{"id":1158},"The end month is the newest date in the data unless an override month is chosen. Twelve EDATE formulas count back from it, SUMIFS totals each month and the prior year, and the chart reads the fixed helper block, so its source range never changes.",[1171,1172],"rect",{"x":1173,"y":289,"width":1174,"height":1175,"fill":1176},"4","752","160","#ffffff",[1171,1178],{"x":1179,"y":1180,"width":1181,"height":1182,"rx":861,"fill":1183,"stroke":1184,"style":1185},"20.0","16.0","115.2","128.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[1187,1188,1190],"g",{"transform":1189},"translate(65.6,37.8) scale(1.000)",[1191,1192],"path",{"d":1193,"fill":1194,"stroke":1195,"style":1196},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1198,1199,1203],"text",{"x":1200,"y":1201,"style":1202},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Data table",[1198,1205,1208],{"x":1200,"y":1206,"style":1207},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","one row per",[1198,1210,1212],{"x":1200,"y":1211,"style":1207},"121.2","month",[46,1214],{"x1":1215,"y1":1216,"x2":1217,"y2":1216,"stroke":1218,"style":1185},"140.2","80.0","162.2","var(--brand,#5b5cf0)",[1220,1221],"polygon",{"points":1222,"fill":1223},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[1171,1225],{"x":1226,"y":1180,"width":1181,"height":1182,"rx":861,"fill":1227,"stroke":1218,"style":1185},"171.2","#f0f4ff",[1187,1229,1231],{"transform":1230},"translate(216.8,37.8) scale(1.000)",[1191,1232],{"d":1233,"fill":1194,"stroke":1234,"style":1196},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","var(--brand-strong,#4338ca)",[1198,1236,1239],{"x":1237,"y":1201,"style":1238},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","End month",[1198,1241,1242],{"x":1237,"y":1206,"style":1207},"MAX or",[1198,1244,1245],{"x":1237,"y":1211,"style":1207},"override",[46,1247],{"x1":1248,"y1":1216,"x2":1249,"y2":1216,"stroke":1218,"style":1185},"291.4","313.4",[1220,1251],{"points":1252,"fill":1223},"317.4,80.0 308.4,85.0 308.4,75.0",[1171,1254],{"x":1255,"y":1180,"width":1181,"height":1182,"rx":861,"fill":1256,"stroke":1257,"style":1185},"322.4","#fdefd8","var(--gold,#b4740a)",[1187,1259,1261],{"transform":1260},"translate(368.0,29.0) scale(1.000)",[1191,1262],{"d":1263,"fill":1194,"stroke":1264,"style":1196},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--gold-ink,#7a4e06)",[1198,1266,1270],{"x":1267,"y":1268,"style":1269},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","12 EDATE",[1198,1272,1274],{"x":1267,"y":1273,"style":1269},"93.0","rows",[1198,1276,1278],{"x":1267,"y":1277,"style":1207},"112.5","count back 11",[1198,1280,1282],{"x":1267,"y":1281,"style":1207},"130.0","months",[46,1284],{"x1":1285,"y1":1216,"x2":1286,"y2":1216,"stroke":1218,"style":1185},"442.6","464.6",[1220,1288],{"points":1289,"fill":1223},"468.6,80.0 459.6,85.0 459.6,75.0",[1171,1291],{"x":1292,"y":1180,"width":1181,"height":1182,"rx":861,"fill":1293,"stroke":1294,"style":1185},"473.6","#d9f4f1","var(--teal,#0f9488)",[1187,1296,1298],{"transform":1297},"translate(519.2,37.8) scale(1.000)",[1191,1299],{"d":1300,"fill":1194,"stroke":1301,"style":1196},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","var(--teal-ink,#0b6157)",[1198,1303,21],{"x":1304,"y":1201,"style":1305},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[1198,1307,1308],{"x":1304,"y":1206,"style":1207},"this year, last",[1198,1310,1311],{"x":1304,"y":1211,"style":1207},"year",[46,1313],{"x1":1314,"y1":1216,"x2":1315,"y2":1216,"stroke":1218,"style":1185},"593.8","615.8",[1220,1317],{"points":1318,"fill":1223},"619.8,80.0 610.8,85.0 610.8,75.0",[1171,1320],{"x":1321,"y":1180,"width":1181,"height":1182,"rx":861,"fill":1227,"stroke":1218,"style":1185},"624.8",[1187,1323,1325],{"transform":1324},"translate(670.4,37.8) scale(1.000)",[1191,1326],{"d":1327,"fill":1194,"stroke":1234,"style":1196},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[1198,1329,1331],{"x":1330,"y":1201,"style":1238},"682.4","Chart",[1198,1333,1334],{"x":1330,"y":1206,"style":1207},"fixed source",[1198,1336,1337],{"x":1330,"y":1211,"style":1207},"range",[30,1339,1341],{"id":1340},"step-3-the-chart-reads-the-fixed-block","Step 3 — The chart reads the fixed block",[10,1343,1344,1345,1348,1349,1352],{},"The chart's series refer to ",[19,1346,1347],{},"B6:B17"," and ",[19,1350,1351],{},"C6:C17"," — cells that never move — so it never needs editing:",[35,1354,1356],{"className":82,"code":1355,"language":84,"meta":40,"style":40},"from openpyxl.chart import BarChart, LineChart, Reference\n\ndef add_chart(ws):\n    cats = Reference(ws, min_col=1, min_row=6, max_row=17)\n    bars = BarChart()\n    bars.add_data(Reference(ws, min_col=2, min_row=5, max_row=17), titles_from_data=True)\n    bars.set_categories(cats)\n    line = LineChart()\n    line.add_data(Reference(ws, min_col=3, min_row=5, max_row=17), titles_from_data=True)\n    line.series[0].marker.symbol = \"none\"\n    bars += line\n    bars.title, bars.y_axis.title = \"Revenue, latest 12 months\", \"Revenue\"\n    bars.y_axis.numFmt = \"#,##0\"\n    bars.x_axis.number_format = \"mmm yy\"\n    bars.x_axis.delete = bars.y_axis.delete = False\n    bars.legend.position = \"b\"\n    bars.width, bars.height = 22, 9\n    ws.add_chart(bars, \"E2\")\n\ndef build(path, months):\n    wb = Workbook()\n    df = sales_data(months)\n    ws = dashboard(wb, df)\n    data_sheet(wb, df)\n    add_chart(ws)\n    wb.save(path)\n\nbuild(\"rolling.xlsx\", 30)\n",[19,1357,1358,1370,1374,1384,1423,1433,1473,1478,1488,1526,1541,1551,1566,1575,1584,1599,1609,1624,1634,1638,1648,1658,1668,1677,1682,1687,1692,1697],{"__ignoreMap":40},[44,1359,1360,1362,1365,1367],{"class":46,"line":47},[44,1361,121],{"class":91},[44,1363,1364],{"class":95}," openpyxl.chart ",[44,1366,92],{"class":91},[44,1368,1369],{"class":95}," BarChart, LineChart, Reference\n",[44,1371,1372],{"class":46,"line":105},[44,1373,149],{"emptyLinePlaceholder":148},[44,1375,1376,1378,1381],{"class":46,"line":118},[44,1377,155],{"class":91},[44,1379,1380],{"class":158}," add_chart",[44,1382,1383],{"class":95},"(ws):\n",[44,1385,1386,1389,1391,1394,1397,1399,1401,1403,1406,1408,1411,1413,1416,1418,1421],{"class":46,"line":132},[44,1387,1388],{"class":95},"    cats ",[44,1390,171],{"class":91},[44,1392,1393],{"class":95}," Reference(ws, ",[44,1395,1396],{"class":183},"min_col",[44,1398,171],{"class":91},[44,1400,420],{"class":225},[44,1402,180],{"class":95},[44,1404,1405],{"class":183},"min_row",[44,1407,171],{"class":91},[44,1409,1410],{"class":225},"6",[44,1412,180],{"class":95},[44,1414,1415],{"class":183},"max_row",[44,1417,171],{"class":91},[44,1419,1420],{"class":225},"17",[44,1422,200],{"class":95},[44,1424,1425,1428,1430],{"class":46,"line":145},[44,1426,1427],{"class":95},"    bars ",[44,1429,171],{"class":91},[44,1431,1432],{"class":95}," BarChart()\n",[44,1434,1435,1438,1440,1442,1444,1446,1448,1450,1453,1455,1457,1459,1461,1464,1467,1469,1471],{"class":46,"line":152},[44,1436,1437],{"class":95},"    bars.add_data(Reference(ws, ",[44,1439,1396],{"class":183},[44,1441,171],{"class":91},[44,1443,303],{"class":225},[44,1445,180],{"class":95},[44,1447,1405],{"class":183},[44,1449,171],{"class":91},[44,1451,1452],{"class":225},"5",[44,1454,180],{"class":95},[44,1456,1415],{"class":183},[44,1458,171],{"class":91},[44,1460,1420],{"class":225},[44,1462,1463],{"class":95},"), ",[44,1465,1466],{"class":183},"titles_from_data",[44,1468,171],{"class":91},[44,1470,504],{"class":225},[44,1472,200],{"class":95},[44,1474,1475],{"class":46,"line":165},[44,1476,1477],{"class":95},"    bars.set_categories(cats)\n",[44,1479,1480,1483,1485],{"class":46,"line":203},[44,1481,1482],{"class":95},"    line ",[44,1484,171],{"class":91},[44,1486,1487],{"class":95}," LineChart()\n",[44,1489,1490,1493,1495,1497,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524],{"class":46,"line":214},[44,1491,1492],{"class":95},"    line.add_data(Reference(ws, ",[44,1494,1396],{"class":183},[44,1496,171],{"class":91},[44,1498,1499],{"class":225},"3",[44,1501,180],{"class":95},[44,1503,1405],{"class":183},[44,1505,171],{"class":91},[44,1507,1452],{"class":225},[44,1509,180],{"class":95},[44,1511,1415],{"class":183},[44,1513,171],{"class":91},[44,1515,1420],{"class":225},[44,1517,1463],{"class":95},[44,1519,1466],{"class":183},[44,1521,171],{"class":91},[44,1523,504],{"class":225},[44,1525,200],{"class":95},[44,1527,1528,1531,1533,1536,1538],{"class":46,"line":231},[44,1529,1530],{"class":95},"    line.series[",[44,1532,289],{"class":225},[44,1534,1535],{"class":95},"].marker.symbol ",[44,1537,171],{"class":91},[44,1539,1540],{"class":54}," \"none\"\n",[44,1542,1543,1545,1548],{"class":46,"line":308},[44,1544,1427],{"class":95},[44,1546,1547],{"class":91},"+=",[44,1549,1550],{"class":95}," line\n",[44,1552,1553,1556,1558,1561,1563],{"class":46,"line":329},[44,1554,1555],{"class":95},"    bars.title, bars.y_axis.title ",[44,1557,171],{"class":91},[44,1559,1560],{"class":54}," \"Revenue, latest 12 months\"",[44,1562,180],{"class":95},[44,1564,1565],{"class":54},"\"Revenue\"\n",[44,1567,1568,1571,1573],{"class":46,"line":334},[44,1569,1570],{"class":95},"    bars.y_axis.numFmt ",[44,1572,171],{"class":91},[44,1574,1066],{"class":54},[44,1576,1577,1580,1582],{"class":46,"line":345},[44,1578,1579],{"class":95},"    bars.x_axis.number_format ",[44,1581,171],{"class":91},[44,1583,1024],{"class":54},[44,1585,1586,1589,1591,1594,1596],{"class":46,"line":361},[44,1587,1588],{"class":95},"    bars.x_axis.delete ",[44,1590,171],{"class":91},[44,1592,1593],{"class":95}," bars.y_axis.delete ",[44,1595,171],{"class":91},[44,1597,1598],{"class":225}," False\n",[44,1600,1601,1604,1606],{"class":46,"line":376},[44,1602,1603],{"class":95},"    bars.legend.position ",[44,1605,171],{"class":91},[44,1607,1608],{"class":54}," \"b\"\n",[44,1610,1611,1614,1616,1619,1621],{"class":46,"line":402},[44,1612,1613],{"class":95},"    bars.width, bars.height ",[44,1615,171],{"class":91},[44,1617,1618],{"class":225}," 22",[44,1620,180],{"class":95},[44,1622,1623],{"class":225},"9\n",[44,1625,1626,1629,1632],{"class":46,"line":414},[44,1627,1628],{"class":95},"    ws.add_chart(bars, ",[44,1630,1631],{"class":54},"\"E2\"",[44,1633,200],{"class":95},[44,1635,1636],{"class":46,"line":431},[44,1637,149],{"emptyLinePlaceholder":148},[44,1639,1640,1642,1645],{"class":46,"line":478},[44,1641,155],{"class":91},[44,1643,1644],{"class":158}," build",[44,1646,1647],{"class":95},"(path, months):\n",[44,1649,1650,1653,1655],{"class":46,"line":509},[44,1651,1652],{"class":95},"    wb ",[44,1654,171],{"class":91},[44,1656,1657],{"class":95}," Workbook()\n",[44,1659,1660,1663,1665],{"class":46,"line":515},[44,1661,1662],{"class":95},"    df ",[44,1664,171],{"class":91},[44,1666,1667],{"class":95}," sales_data(months)\n",[44,1669,1670,1672,1674],{"class":46,"line":523},[44,1671,348],{"class":95},[44,1673,171],{"class":91},[44,1675,1676],{"class":95}," dashboard(wb, df)\n",[44,1678,1679],{"class":46,"line":528},[44,1680,1681],{"class":95},"    data_sheet(wb, df)\n",[44,1683,1684],{"class":46,"line":544},[44,1685,1686],{"class":95},"    add_chart(ws)\n",[44,1688,1689],{"class":46,"line":1109},[44,1690,1691],{"class":95},"    wb.save(path)\n",[44,1693,1695],{"class":46,"line":1694},27,[44,1696,149],{"emptyLinePlaceholder":148},[44,1698,1700,1703,1706,1708,1710],{"class":46,"line":1699},28,[44,1701,1702],{"class":95},"build(",[44,1704,1705],{"class":54},"\"rolling.xlsx\"",[44,1707,180],{"class":95},[44,1709,539],{"class":225},[44,1711,200],{"class":95},[10,1713,1714,1715,1718,1719,1722,1723,28],{},"The bars are this year's revenue and the line the same months a year earlier, combined in one chart with ",[19,1716,1717],{},"bars += line",", so seasonality and growth are visible at a glance. Setting ",[19,1720,1721],{},"delete = False"," on both axes matters: without it, recent Excel versions hide the axes of charts written by openpyxl. More on combining chart types is in ",[24,1724,1726],{"href":1725},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-combo-chart-with-a-secondary-axis-in-openpyxl\u002F","Add a Combo Chart with a Secondary Axis in openpyxl",[1152,1728,1161,1734,1161,1737,1161,1740,1161,1744,1161,1750,1161,1756,1161,1759,1161,1763,1161,1769,1161,1774,1161,1778,1161,1782,1161,1785,1161,1789,1161,1792,1161,1796,1161,1799,1161,1803,1161,1806,1161,1810,1161,1814,1161,1818,1161,1822,1161,1826,1161,1830,1161,1834,1161,1838,1161,1842,1161,1846,1161,1850,1161,1854,1161,1858,1161,1863,1161,1866,1161,1869,1161,1872,1161,1875,1161,1878,1161,1881,1161,1884,1161,1887,1161,1890,1161,1893,1161,1896,1161,1899,1161,1902,1161,1905,1161,1908,1161,1911,1161,1914,1161,1917,1161,1920,1161,1922,1161,1925,1161,1928,1161,1931],{"viewBox":1729,"role":1155,"ariaLabelledBy":1730,"xmlns":1159,"style":1733},"4 0 748 280",[1731,1732],"r12-chart-t","r12-chart-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[1163,1735,1736],{"id":1731},"Twelve months to June 2026 against the year before",[1167,1738,1739],{"id":1732},"Bars show revenue in thousands for July 2025 to June 2026 and the line shows the same months a year earlier. Revenue is higher than a year before in every month and both follow the same seasonal dip in autumn.",[1171,1741],{"x":1173,"y":289,"width":1742,"height":1743,"fill":1176},"748","280",[1171,1745],{"x":1746,"y":1747,"width":1748,"height":1748,"rx":1499,"fill":1227,"stroke":1218,"style":1749},"64.0","18.0","14.0","stroke-width:1.5px",[1198,1751,1755],{"x":1752,"y":1753,"style":1754},"84.0","29.5","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","This year",[1171,1757],{"x":1758,"y":1747,"width":1748,"height":1748,"rx":1499,"fill":1227,"stroke":1218,"style":1749},"169.3",[1198,1760,1762],{"x":1761,"y":1753,"style":1754},"189.3","Last year",[46,1764],{"x1":1765,"y1":1766,"x2":1767,"y2":1766,"stroke":1184,"style":1768},"46.7","242.0","736.0","stroke-width:1px",[1198,1770,289],{"x":1771,"y":1772,"style":1773},"27.4","246.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[46,1775],{"x1":1765,"y1":1776,"x2":1767,"y2":1776,"stroke":1184,"style":1777},"194.5","stroke-width:1px;stroke-dasharray:5 4",[1198,1779,1781],{"x":1771,"y":1780,"style":1773},"198.5","20",[46,1783],{"x1":1765,"y1":1784,"x2":1767,"y2":1784,"stroke":1184,"style":1777},"147.0",[1198,1786,1788],{"x":1771,"y":1787,"style":1773},"151.0","40",[46,1790],{"x1":1765,"y1":1791,"x2":1767,"y2":1791,"stroke":1184,"style":1777},"99.5",[1198,1793,1795],{"x":1771,"y":1794,"style":1773},"103.5","60",[46,1797],{"x1":1765,"y1":1798,"x2":1767,"y2":1798,"stroke":1184,"style":1777},"52.0",[1198,1800,1802],{"x":1771,"y":1801,"style":1773},"56.0","80",[46,1804],{"x1":1765,"y1":1766,"x2":1767,"y2":1766,"stroke":1805,"style":1749},"var(--muted,#5b6780)",[1198,1807,1809],{"x":1268,"y":1808,"style":1207},"260.0","Jul",[1198,1811,1813],{"x":1812,"y":1808,"style":1207},"132.9","Aug",[1198,1815,1817],{"x":1816,"y":1808,"style":1207},"190.3","Sep",[1198,1819,1821],{"x":1820,"y":1808,"style":1207},"247.8","Oct",[1198,1823,1825],{"x":1824,"y":1808,"style":1207},"305.2","Nov",[1198,1827,1829],{"x":1828,"y":1808,"style":1207},"362.7","Dec",[1198,1831,1833],{"x":1832,"y":1808,"style":1207},"420.1","Jan",[1198,1835,1837],{"x":1836,"y":1808,"style":1207},"477.5","Feb",[1198,1839,1841],{"x":1840,"y":1808,"style":1207},"535.0","Mar",[1198,1843,1845],{"x":1844,"y":1808,"style":1207},"592.4","Apr",[1198,1847,1849],{"x":1848,"y":1808,"style":1207},"649.8","May",[1198,1851,1853],{"x":1852,"y":1808,"style":1207},"707.3","Jun",[1191,1855],{"d":1856,"fill":1194,"stroke":1218,"style":1857},"M75.5,115.4 L132.9,118.0 L190.3,123.2 L247.8,117.3 L305.2,116.8 L362.7,104.0 L420.1,95.2 L477.5,87.1 L535.0,88.1 L592.4,76.9 L649.8,75.0 L707.3,77.4","stroke-width:2.5px;stroke-linejoin:round;stroke-linecap:round",[1859,1860],"circle",{"cx":1268,"cy":1861,"r":1862,"fill":1176,"stroke":1218,"style":1185},"115.4","4.0",[1859,1864],{"cx":1812,"cy":1865,"r":1862,"fill":1176,"stroke":1218,"style":1185},"118.0",[1859,1867],{"cx":1816,"cy":1868,"r":1862,"fill":1176,"stroke":1218,"style":1185},"123.2",[1859,1870],{"cx":1820,"cy":1871,"r":1862,"fill":1176,"stroke":1218,"style":1185},"117.3",[1859,1873],{"cx":1824,"cy":1874,"r":1862,"fill":1176,"stroke":1218,"style":1185},"116.8",[1859,1876],{"cx":1828,"cy":1877,"r":1862,"fill":1176,"stroke":1218,"style":1185},"104.0",[1859,1879],{"cx":1832,"cy":1880,"r":1862,"fill":1176,"stroke":1218,"style":1185},"95.2",[1859,1882],{"cx":1836,"cy":1883,"r":1862,"fill":1176,"stroke":1218,"style":1185},"87.1",[1859,1885],{"cx":1840,"cy":1886,"r":1862,"fill":1176,"stroke":1218,"style":1185},"88.1",[1859,1888],{"cx":1844,"cy":1889,"r":1862,"fill":1176,"stroke":1218,"style":1185},"76.9",[1859,1891],{"cx":1848,"cy":1892,"r":1862,"fill":1176,"stroke":1218,"style":1185},"75.0",[1859,1894],{"cx":1852,"cy":1895,"r":1862,"fill":1176,"stroke":1218,"style":1185},"77.4",[1191,1897],{"d":1898,"fill":1194,"stroke":1218,"style":1857},"M75.5,133.9 L132.9,134.4 L190.3,143.9 L247.8,144.1 L305.2,136.3 L362.7,129.4 L420.1,120.9 L477.5,115.4 L535.0,104.7 L592.4,98.3 L649.8,105.2 L707.3,105.2",[1859,1900],{"cx":1268,"cy":1901,"r":1862,"fill":1176,"stroke":1218,"style":1185},"133.9",[1859,1903],{"cx":1812,"cy":1904,"r":1862,"fill":1176,"stroke":1218,"style":1185},"134.4",[1859,1906],{"cx":1816,"cy":1907,"r":1862,"fill":1176,"stroke":1218,"style":1185},"143.9",[1859,1909],{"cx":1820,"cy":1910,"r":1862,"fill":1176,"stroke":1218,"style":1185},"144.1",[1859,1912],{"cx":1824,"cy":1913,"r":1862,"fill":1176,"stroke":1218,"style":1185},"136.3",[1859,1915],{"cx":1828,"cy":1916,"r":1862,"fill":1176,"stroke":1218,"style":1185},"129.4",[1859,1918],{"cx":1832,"cy":1919,"r":1862,"fill":1176,"stroke":1218,"style":1185},"120.9",[1859,1921],{"cx":1836,"cy":1861,"r":1862,"fill":1176,"stroke":1218,"style":1185},[1859,1923],{"cx":1840,"cy":1924,"r":1862,"fill":1176,"stroke":1218,"style":1185},"104.7",[1859,1926],{"cx":1844,"cy":1927,"r":1862,"fill":1176,"stroke":1218,"style":1185},"98.3",[1859,1929],{"cx":1848,"cy":1930,"r":1862,"fill":1176,"stroke":1218,"style":1185},"105.2",[1859,1932],{"cx":1852,"cy":1930,"r":1862,"fill":1176,"stroke":1218,"style":1185},[30,1934,1936],{"id":1935},"step-4-check-that-the-window-moves","Step 4 — Check that the window moves",[10,1938,1939],{},"Recalculate the workbook with LibreOffice, read the helper block, and compare it with the same window computed in pandas. Then build again with three more months of data, and once more with an override, and see where the window lands:",[35,1941,1943],{"className":82,"code":1942,"language":84,"meta":40,"style":40},"import subprocess\nfrom openpyxl import load_workbook\n\ndef recalc(path):\n    subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"calc\", path], check=True, capture_output=True)\n    return load_workbook(f\"calc\u002F{path}\", data_only=True)[\"Dashboard\"]\n\ndef window(ws):\n    return [(ws[f\"A{r}\"].value, ws[f\"B{r}\"].value, ws[f\"C{r}\"].value) for r in range(6, 18)]\n\nw = window(recalc(\"rolling.xlsx\"))\nprint(\"window:\", w[0][0].strftime(\"%b %Y\"), \"to\", w[-1][0].strftime(\"%b %Y\"))\nexpected = sales.set_index(\"Month\")[\"Revenue\"].iloc[-12:]\nprint(\"matches pandas:\", [b for _, b, _ in w] == expected.tolist())\nprint(\"prior year, first and last:\", w[0][2], w[-1][2])\n\nbuild(\"rolling.xlsx\", 33)\nw = window(recalc(\"rolling.xlsx\"))\nprint(\"after 3 more months:\", w[0][0].strftime(\"%b %Y\"), \"to\", w[-1][0].strftime(\"%b %Y\"))\n\nwb = load_workbook(\"rolling.xlsx\")\nwb[\"Dashboard\"][\"B3\"] = pd.Timestamp(\"2025-06-01\").to_pydatetime()\nwb.save(\"rolling.xlsx\")\nw = window(recalc(\"rolling.xlsx\"))\nprint(\"with override:\", w[0][0].strftime(\"%b %Y\"), \"to\", w[-1][0].strftime(\"%b %Y\"), \"| first prior-year cell:\", w[0][2])\n",[19,1944,1945,1952,1963,1967,1977,2031,2069,2073,2082,2155,2159,2173,2228,2254,2282,2312,2316,2329,2341,2390,2394,2407,2431,2440,2452],{"__ignoreMap":40},[44,1946,1947,1949],{"class":46,"line":47},[44,1948,92],{"class":91},[44,1950,1951],{"class":95}," subprocess\n",[44,1953,1954,1956,1958,1960],{"class":46,"line":105},[44,1955,121],{"class":91},[44,1957,124],{"class":95},[44,1959,92],{"class":91},[44,1961,1962],{"class":95}," load_workbook\n",[44,1964,1965],{"class":46,"line":118},[44,1966,149],{"emptyLinePlaceholder":148},[44,1968,1969,1971,1974],{"class":46,"line":132},[44,1970,155],{"class":91},[44,1972,1973],{"class":158}," recalc",[44,1975,1976],{"class":95},"(path):\n",[44,1978,1979,1982,1985,1987,1990,1992,1995,1997,2000,2002,2005,2007,2010,2013,2016,2018,2020,2022,2025,2027,2029],{"class":46,"line":145},[44,1980,1981],{"class":95},"    subprocess.run([",[44,1983,1984],{"class":54},"\"soffice\"",[44,1986,180],{"class":95},[44,1988,1989],{"class":54},"\"--headless\"",[44,1991,180],{"class":95},[44,1993,1994],{"class":54},"\"--convert-to\"",[44,1996,180],{"class":95},[44,1998,1999],{"class":54},"\"xlsx\"",[44,2001,180],{"class":95},[44,2003,2004],{"class":54},"\"--outdir\"",[44,2006,180],{"class":95},[44,2008,2009],{"class":54},"\"calc\"",[44,2011,2012],{"class":95},", path], ",[44,2014,2015],{"class":183},"check",[44,2017,171],{"class":91},[44,2019,504],{"class":225},[44,2021,180],{"class":95},[44,2023,2024],{"class":183},"capture_output",[44,2026,171],{"class":91},[44,2028,504],{"class":225},[44,2030,200],{"class":95},[44,2032,2033,2035,2038,2040,2043,2045,2047,2049,2051,2053,2056,2058,2060,2063,2066],{"class":46,"line":152},[44,2034,311],{"class":91},[44,2036,2037],{"class":95}," load_workbook(",[44,2039,457],{"class":91},[44,2041,2042],{"class":54},"\"calc\u002F",[44,2044,464],{"class":463},[44,2046,1191],{"class":95},[44,2048,470],{"class":463},[44,2050,473],{"class":54},[44,2052,180],{"class":95},[44,2054,2055],{"class":183},"data_only",[44,2057,171],{"class":91},[44,2059,504],{"class":225},[44,2061,2062],{"class":95},")[",[44,2064,2065],{"class":54},"\"Dashboard\"",[44,2067,2068],{"class":95},"]\n",[44,2070,2071],{"class":46,"line":165},[44,2072,149],{"emptyLinePlaceholder":148},[44,2074,2075,2077,2080],{"class":46,"line":203},[44,2076,155],{"class":91},[44,2078,2079],{"class":158}," window",[44,2081,1383],{"class":95},[44,2083,2084,2086,2089,2091,2093,2095,2097,2099,2101,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126,2128,2130,2133,2136,2139,2141,2143,2145,2147,2149,2152],{"class":46,"line":214},[44,2085,311],{"class":91},[44,2087,2088],{"class":95}," [(ws[",[44,2090,457],{"class":91},[44,2092,888],{"class":54},[44,2094,464],{"class":463},[44,2096,893],{"class":95},[44,2098,470],{"class":463},[44,2100,473],{"class":54},[44,2102,2103],{"class":95},"].value, ws[",[44,2105,457],{"class":91},[44,2107,931],{"class":54},[44,2109,464],{"class":463},[44,2111,893],{"class":95},[44,2113,470],{"class":463},[44,2115,473],{"class":54},[44,2117,2103],{"class":95},[44,2119,457],{"class":91},[44,2121,965],{"class":54},[44,2123,464],{"class":463},[44,2125,893],{"class":95},[44,2127,470],{"class":463},[44,2129,473],{"class":54},[44,2131,2132],{"class":95},"].value) ",[44,2134,2135],{"class":91},"for",[44,2137,2138],{"class":95}," r ",[44,2140,385],{"class":91},[44,2142,855],{"class":225},[44,2144,858],{"class":95},[44,2146,1410],{"class":225},[44,2148,180],{"class":95},[44,2150,2151],{"class":225},"18",[44,2153,2154],{"class":95},")]\n",[44,2156,2157],{"class":46,"line":231},[44,2158,149],{"emptyLinePlaceholder":148},[44,2160,2161,2164,2166,2169,2171],{"class":46,"line":308},[44,2162,2163],{"class":95},"w ",[44,2165,171],{"class":91},[44,2167,2168],{"class":95}," window(recalc(",[44,2170,1705],{"class":54},[44,2172,570],{"class":95},[44,2174,2175,2177,2179,2182,2185,2187,2190,2192,2195,2197,2200,2203,2205,2208,2210,2212,2214,2216,2218,2220,2222,2224,2226],{"class":46,"line":329},[44,2176,547],{"class":225},[44,2178,858],{"class":95},[44,2180,2181],{"class":54},"\"window:\"",[44,2183,2184],{"class":95},", w[",[44,2186,289],{"class":225},[44,2188,2189],{"class":95},"][",[44,2191,289],{"class":225},[44,2193,2194],{"class":95},"].strftime(",[44,2196,473],{"class":54},[44,2198,2199],{"class":463},"%b",[44,2201,2202],{"class":54}," %Y\"",[44,2204,1463],{"class":95},[44,2206,2207],{"class":54},"\"to\"",[44,2209,2184],{"class":95},[44,2211,300],{"class":91},[44,2213,420],{"class":225},[44,2215,2189],{"class":95},[44,2217,289],{"class":225},[44,2219,2194],{"class":95},[44,2221,473],{"class":54},[44,2223,2199],{"class":463},[44,2225,2202],{"class":54},[44,2227,570],{"class":95},[44,2229,2230,2233,2235,2238,2240,2242,2244,2247,2249,2251],{"class":46,"line":334},[44,2231,2232],{"class":95},"expected ",[44,2234,171],{"class":91},[44,2236,2237],{"class":95}," sales.set_index(",[44,2239,317],{"class":54},[44,2241,2062],{"class":95},[44,2243,323],{"class":54},[44,2245,2246],{"class":95},"].iloc[",[44,2248,300],{"class":91},[44,2250,861],{"class":225},[44,2252,2253],{"class":95},":]\n",[44,2255,2256,2258,2260,2263,2266,2268,2271,2273,2276,2279],{"class":46,"line":345},[44,2257,547],{"class":225},[44,2259,858],{"class":95},[44,2261,2262],{"class":54},"\"matches pandas:\"",[44,2264,2265],{"class":95},", [b ",[44,2267,2135],{"class":91},[44,2269,2270],{"class":95}," _, b, _ ",[44,2272,385],{"class":91},[44,2274,2275],{"class":95}," w] ",[44,2277,2278],{"class":91},"==",[44,2280,2281],{"class":95}," expected.tolist())\n",[44,2283,2284,2286,2288,2291,2293,2295,2297,2299,2302,2304,2306,2308,2310],{"class":46,"line":361},[44,2285,547],{"class":225},[44,2287,858],{"class":95},[44,2289,2290],{"class":54},"\"prior year, first and last:\"",[44,2292,2184],{"class":95},[44,2294,289],{"class":225},[44,2296,2189],{"class":95},[44,2298,303],{"class":225},[44,2300,2301],{"class":95},"], w[",[44,2303,300],{"class":91},[44,2305,420],{"class":225},[44,2307,2189],{"class":95},[44,2309,303],{"class":225},[44,2311,373],{"class":95},[44,2313,2314],{"class":46,"line":376},[44,2315,149],{"emptyLinePlaceholder":148},[44,2317,2318,2320,2322,2324,2327],{"class":46,"line":402},[44,2319,1702],{"class":95},[44,2321,1705],{"class":54},[44,2323,180],{"class":95},[44,2325,2326],{"class":225},"33",[44,2328,200],{"class":95},[44,2330,2331,2333,2335,2337,2339],{"class":46,"line":414},[44,2332,2163],{"class":95},[44,2334,171],{"class":91},[44,2336,2168],{"class":95},[44,2338,1705],{"class":54},[44,2340,570],{"class":95},[44,2342,2343,2345,2347,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2376,2378,2380,2382,2384,2386,2388],{"class":46,"line":431},[44,2344,547],{"class":225},[44,2346,858],{"class":95},[44,2348,2349],{"class":54},"\"after 3 more months:\"",[44,2351,2184],{"class":95},[44,2353,289],{"class":225},[44,2355,2189],{"class":95},[44,2357,289],{"class":225},[44,2359,2194],{"class":95},[44,2361,473],{"class":54},[44,2363,2199],{"class":463},[44,2365,2202],{"class":54},[44,2367,1463],{"class":95},[44,2369,2207],{"class":54},[44,2371,2184],{"class":95},[44,2373,300],{"class":91},[44,2375,420],{"class":225},[44,2377,2189],{"class":95},[44,2379,289],{"class":225},[44,2381,2194],{"class":95},[44,2383,473],{"class":54},[44,2385,2199],{"class":463},[44,2387,2202],{"class":54},[44,2389,570],{"class":95},[44,2391,2392],{"class":46,"line":478},[44,2393,149],{"emptyLinePlaceholder":148},[44,2395,2396,2399,2401,2403,2405],{"class":46,"line":509},[44,2397,2398],{"class":95},"wb ",[44,2400,171],{"class":91},[44,2402,2037],{"class":95},[44,2404,1705],{"class":54},[44,2406,200],{"class":95},[44,2408,2409,2412,2414,2416,2418,2420,2422,2425,2428],{"class":46,"line":515},[44,2410,2411],{"class":95},"wb[",[44,2413,2065],{"class":54},[44,2415,2189],{"class":95},[44,2417,741],{"class":54},[44,2419,699],{"class":95},[44,2421,171],{"class":91},[44,2423,2424],{"class":95}," pd.Timestamp(",[44,2426,2427],{"class":54},"\"2025-06-01\"",[44,2429,2430],{"class":95},").to_pydatetime()\n",[44,2432,2433,2436,2438],{"class":46,"line":523},[44,2434,2435],{"class":95},"wb.save(",[44,2437,1705],{"class":54},[44,2439,200],{"class":95},[44,2441,2442,2444,2446,2448,2450],{"class":46,"line":528},[44,2443,2163],{"class":95},[44,2445,171],{"class":91},[44,2447,2168],{"class":95},[44,2449,1705],{"class":54},[44,2451,570],{"class":95},[44,2453,2454,2456,2458,2461,2463,2465,2467,2469,2471,2473,2475,2477,2479,2481,2483,2485,2487,2489,2491,2493,2495,2497,2499,2501,2504,2506,2508,2510,2512],{"class":46,"line":544},[44,2455,547],{"class":225},[44,2457,858],{"class":95},[44,2459,2460],{"class":54},"\"with override:\"",[44,2462,2184],{"class":95},[44,2464,289],{"class":225},[44,2466,2189],{"class":95},[44,2468,289],{"class":225},[44,2470,2194],{"class":95},[44,2472,473],{"class":54},[44,2474,2199],{"class":463},[44,2476,2202],{"class":54},[44,2478,1463],{"class":95},[44,2480,2207],{"class":54},[44,2482,2184],{"class":95},[44,2484,300],{"class":91},[44,2486,420],{"class":225},[44,2488,2189],{"class":95},[44,2490,289],{"class":225},[44,2492,2194],{"class":95},[44,2494,473],{"class":54},[44,2496,2199],{"class":463},[44,2498,2202],{"class":54},[44,2500,1463],{"class":95},[44,2502,2503],{"class":54},"\"| first prior-year cell:\"",[44,2505,2184],{"class":95},[44,2507,289],{"class":225},[44,2509,2189],{"class":95},[44,2511,303],{"class":225},[44,2513,373],{"class":95},[10,2515,2516,2517,2519,2520,28],{},"With thirty months, the window runs from July 2025 to June 2026 and matches the pandas calculation exactly; the prior-year line starts at 45,500 for July 2024 and ends at 57,600 for June 2025. With three more months of data the same formulas show October 2025 to September 2026 — the chart moved without being touched. With the override set to June 2025 the window shows July 2024 to June 2025, and the first prior-year cell, July 2023, is ",[19,2518,1141],{}," (read back as the error string), because the data starts in January 2024: the chart simply leaves that point out. Recalculating with LibreOffice this way is a cheap regression test for any formula-driven dashboard; the general technique is covered in ",[24,2521,2523],{"href":2522},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frecalculate-excel-formulas-without-excel-in-python\u002F","Recalculate Excel Formulas Without Excel in Python",[1152,2525,1161,2530,1161,2533,1161,2536,1161,2539,1161,2545,1161,2549,1161,2555,1161,2560,1161,2563,1161,2566,1161,2570,1161,2575,1161,2579,1161,2581,1161,2583,1161,2586,1161,2591,1161,2594,1161,2596],{"viewBox":2526,"role":1155,"ariaLabelledBy":2527,"xmlns":1159,"style":1160},"4 0 752 211",[2528,2529],"r12-moves-t","r12-moves-d",[1163,2531,2532],{"id":2528},"The same formulas, three different windows",[1167,2534,2535],{"id":2529},"With data to June 2026 the chart shows July 2025 to June 2026. After three more months are added it shows October 2025 to September 2026. With the override set to June 2025 it shows July 2024 to June 2025.",[1171,2537],{"x":1173,"y":289,"width":1174,"height":2538,"fill":1176},"211",[46,2540],{"x1":2541,"y1":2542,"x2":2543,"y2":2542,"stroke":1805,"style":2544},"96.0","105.5","664.0","stroke-width:2.5px",[1171,2546],{"x":1179,"y":1180,"width":2547,"height":2548,"rx":861,"fill":1256,"stroke":1257,"style":1185},"176.0","57.5",[1198,2550,2554],{"x":2551,"y":2552,"style":2553},"108.0","40.5","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Jul 24 – Jun 25",[1198,2556,2559],{"x":2551,"y":2557,"style":2558},"59.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","override set to Jun 2025",[46,2561],{"x1":2551,"y1":2562,"x2":2551,"y2":2542,"stroke":1805,"style":1749},"73.5",[1859,2564],{"cx":2551,"cy":2542,"r":2565,"fill":1257,"stroke":1176,"style":1185},"7.0",[1171,2567],{"x":2568,"y":2569,"width":2547,"height":2548,"rx":861,"fill":1227,"stroke":1218,"style":1185},"292.0","137.5",[1198,2571,2574],{"x":1267,"y":2572,"style":2573},"162.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Jul 25 – Jun 26",[1198,2576,2578],{"x":1267,"y":2577,"style":2558},"180.5","data to June 2026",[46,2580],{"x1":1267,"y1":2542,"x2":1267,"y2":2569,"stroke":1805,"style":1749},[1859,2582],{"cx":1267,"cy":2542,"r":2565,"fill":1218,"stroke":1176,"style":1185},[1171,2584],{"x":2585,"y":1180,"width":2547,"height":2548,"rx":861,"fill":1293,"stroke":1294,"style":1185},"564.0",[1198,2587,2590],{"x":2588,"y":2552,"style":2589},"652.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Oct 25 – Sep 26",[1198,2592,2593],{"x":2588,"y":2557,"style":2558},"three months added",[46,2595],{"x1":2588,"y1":2562,"x2":2588,"y2":2542,"stroke":1805,"style":1749},[1859,2597],{"cx":2588,"cy":2542,"r":2565,"fill":1294,"stroke":1176,"style":1185},[30,2599,2601],{"id":2600},"why-not-a-dynamic-named-range","Why not a dynamic named range?",[10,2603,2604,2605,2608,2609,2612,2613,2615,2616,2618],{},"A common alternative is to define a name with ",[19,2606,2607],{},"OFFSET"," — something like ",[19,2610,2611],{},"=OFFSET(Data!$B$1,COUNT(Data!$B:$B)-11,0,12,1)"," — and point the chart series at the name. It works in desktop Excel, but it has drawbacks for generated workbooks. ",[19,2614,2607],{}," is volatile, recalculating on every change anywhere in the workbook. Charts that refer to names are not supported the same way by every application that might open the file, including some web and mobile viewers and LibreOffice. And the window then depends on the physical order of the data rows, so a sort or an out-of-order paste silently changes what is plotted. The helper block avoids all three: its formulas are ordinary, its range is fixed, and ",[19,2617,21],{}," finds each month by value, wherever the row is. It also gives readers something visible to check the chart against.",[30,2620,2622],{"id":2621},"common-pitfalls","Common pitfalls",[2624,2625,2626,2640,2649,2658,2664],"ul",{},[2627,2628,2629,2633,2634,1348,2636,2639],"li",{},[2630,2631,2632],"strong",{},"Months stored as text."," ",[19,2635,1126],{},[19,2637,2638],{},"MAX"," need real dates.",[2627,2641,2642,2645,2646,2648],{},[2630,2643,2644],{},"Zeros for missing months."," Return ",[19,2647,1137],{}," so the chart skips the point.",[2627,2650,2651,2654,2655,2657],{},[2630,2652,2653],{},"Hidden axes."," Set ",[19,2656,1721],{}," on openpyxl chart axes.",[2627,2659,2660,2663],{},[2630,2661,2662],{},"Hard-coded chart ranges in the data sheet."," Point the chart at the helper block.",[2627,2665,2666,2633,2669,2671],{},[2630,2667,2668],{},"Duplicate rows for a month.",[19,2670,21],{}," adds them together; deduplicate the data first.",[30,2673,2675],{"id":2674},"conclusion","Conclusion",[10,2677,2678,2679,2681,2682,2684,2685,2687,2688,2690],{},"To give a dashboard a chart that always shows the latest twelve months, anchor a helper block on ",[19,2680,2638],{}," of the data's month column (or an override month), count back with ",[19,2683,1126],{},", total each month with ",[19,2686,21],{}," and use ",[19,2689,1137],{}," where a comparison month has no data. Point the chart at the helper block's fixed cells. New data moves the window by itself, readers can look back by picking a month, and a LibreOffice recalculation confirms the window lands where it should.",[30,2692,2694],{"id":2693},"frequently-asked-questions","Frequently asked questions",[10,2696,2697,2700],{},[2630,2698,2699],{},"How do I make an Excel chart show the last 12 months automatically?","\nBuild a helper block whose month cells count back from the latest date with EDATE, total each month with SUMIFS, and point the chart at that block; new data moves the window without editing the chart.",[10,2702,2703,2706],{},[2630,2704,2705],{},"How do I stop a chart dropping to zero for months with no data?","\nReturn NA() instead of 0 for those cells; charts skip #N\u002FA values rather than plotting them.",[10,2708,2709,2712],{},[2630,2710,2711],{},"Can a dynamic named range with OFFSET drive the chart instead?","\nIn desktop Excel, yes, but it is volatile, depends on row order and is not supported equally by all viewers; a fixed helper block is more robust.",[10,2714,2715,2718],{},[2630,2716,2717],{},"How do I let readers look at an earlier 12-month period?","\nAdd an override cell with a dropdown of months and make the end month IF(override=\"\", MAX(months), override).",[30,2720,2722],{"id":2721},"related","Related",[2624,2724,2725,2731,2738,2745],{},[2627,2726,2727,2728,2730],{},"Up one level: ",[24,2729,27],{"href":26}," — dashboards from Python.",[2627,2732,2733,2737],{},[24,2734,2736],{"href":2735},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs\u002F","Build a Dashboard Sheet with Charts from Multiple Tabs"," — charts on a summary sheet.",[2627,2739,2740,2744],{},[24,2741,2743],{"href":2742},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard\u002F","Add a KPI Summary Block to an Excel Dashboard"," — headline figures above the chart.",[2627,2746,2747,2751],{},[24,2748,2750],{"href":2749},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas\u002F","Calculate Month-to-Date and Year-to-Date Totals with pandas"," — period totals in Python.",[2753,2754,2755],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":40,"searchDepth":105,"depth":105,"links":2757},[2758,2759,2760,2761,2762,2763,2764,2765,2766,2767],{"id":32,"depth":105,"text":33},{"id":78,"depth":105,"text":79},{"id":576,"depth":105,"text":577},{"id":1340,"depth":105,"text":1341},{"id":1935,"depth":105,"text":1936},{"id":2600,"depth":105,"text":2601},{"id":2621,"depth":105,"text":2622},{"id":2674,"depth":105,"text":2675},{"id":2693,"depth":105,"text":2694},{"id":2721,"depth":105,"text":2722},"2026-10-02","Give an Excel dashboard a chart that always shows the latest twelve months, built from Python with openpyxl: a helper block of EDATE and SUMIFS formulas anchored on the newest month in the data, an optional override cell for looking back, a prior-year comparison line, and checks with LibreOffice that the window moves when new months arrive.","md",[2772,2774,2776,2778],{"q":2699,"a":2773},"Build a helper block whose month cells count back from the latest date with EDATE, total each month with SUMIFS, and point the chart at that block; new data moves the window without editing the chart.",{"q":2705,"a":2775},"Return NA() instead of 0 for those cells; charts skip #N\u002FA values rather than plotting them.",{"q":2711,"a":2777},"In desktop Excel, yes, but it is volatile, depends on row order and is not supported equally by all viewers; a fixed helper block is more robust.",{"q":2717,"a":2779},"Add an override cell with a dropdown of months and make the end month IF(override=\"\", MAX(months), override).","rolling 12 month chart excel dashboard",null,{"breadcrumb":2783},[2784,2786,2789],{"name":2785,"item":268},"Home",{"name":2787,"item":2788},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":27,"item":26},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard",{"title":5,"description":2792},"A dashboard chart that follows the latest 12 months: openpyxl helper block with MAX, EDATE and SUMIFS, an override month dropdown, a prior-year line, and verifying the moving window with LibreOffice.","build-a-rolling-12-month-chart-on-an-excel-dashboard","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard\u002Findex",[2796,2797,2798],"charts-and-images","report-layouts","excel-formulas","how-to","SvQJhFddJUQeRoN-VO62GTHAHah3m92ir8ndp534Zq4",[2802,2806],{"title":2803,"path":2804,"stem":2805,"children":-1},"Build a One-Page Executive Summary Sheet","\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet\u002Findex",{"title":2807,"path":2808,"stem":2809,"children":-1},"Lay Out a Dashboard Sheet on a Grid","\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid\u002Findex",[2811,2813],{"path":2812,"title":27},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards",{"path":2814,"title":2787},"\u002Fautomating-reporting-workflows",[],[2817,2820,2823,2826,2829,2832],{"path":2818,"title":2819,"kind":2799},"\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":2821,"title":2822,"kind":2799},"\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":2824,"title":2825,"kind":2799},"\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":2827,"title":2828,"kind":2799},"\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",{"path":2830,"title":2831,"kind":2799},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Flabel-chart-points-from-cell-values-with-xlsxwriter","Label Chart Points from Cell Values with XlsxWriter",{"path":2833,"title":2834,"kind":2835},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl","Creating Charts in Excel with openpyxl","guide",1790972129448]