[{"data":1,"prerenderedAt":3460},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid":3425,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid":3433,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid":3438,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid":3439},{"id":4,"title":5,"body":6,"dateModified":3391,"datePublished":3391,"description":3392,"extension":3393,"faq":3394,"keyword":3403,"kind":3404,"meta":3405,"navigation":114,"path":3414,"seo":3415,"slug":3418,"stem":3419,"topics":3420,"type":3423,"__hash__":3424},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid\u002Findex.md","Lay Out a Dashboard Sheet on a Grid",{"type":7,"value":8,"toc":3379},"minimark",[9,29,34,59,66,70,73,446,453,457,460,1223,1229,1427,1431,1434,2114,2122,2126,2140,2603,2621,2714,2718,2721,3081,3095,3261,3265,3308,3312,3315,3319,3325,3331,3337,3343,3347,3376],[10,11,12,13,17,18,22,23,28],"p",{},"Generated dashboards often look slightly wrong in a way that is hard to name: one chart a few pixels wider than the one beside it, KPI tiles that do not quite line up with the chart edges, a gap on the right that changes with the zoom level. The cause is usually that each element was positioned on its own — a chart anchored at ",[14,15,16],"code",{},"H2"," with a width in centimetres, a tile merged over whatever columns happened to be free — so nothing shares a common measure. Designers solve this with a grid, and an Excel sheet already ",[19,20,21],"em",{},"is"," one. This page makes the sheet's columns and rows a uniform grid, describes every tile and chart by its position and span on that grid, snaps charts to cell boundaries with two-cell anchors so their edges coincide with the tiles', and checks the layout for overlaps before saving. 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\n","bash","",[14,42,43],{"__ignoreMap":40},[44,45,48,52,56],"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\n",[10,60,61,62,65],{},"The examples use openpyxl because it exposes chart anchors directly. The layout ideas — a uniform grid, positions described in grid units, anchors on cell boundaries — apply equally to XlsxWriter, whose ",[14,63,64],{},"insert_chart"," accepts offsets and sizes in pixels you can compute from the same grid.",[30,67,69],{"id":68},"step-1-decide-the-grid","Step 1 — Decide the grid",[10,71,72],{},"A dashboard sheet laid out on 12 or 24 equal columns is easy to divide into halves, thirds and quarters. Set every grid column to the same width and every row to the same height, and keep column A as a narrow margin:",[35,74,78],{"className":75,"code":76,"language":77,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\nfrom openpyxl.utils import get_column_letter\n\nGRID_COLS, COL_WIDTH = 24, 4.7          # character units; about 37 px each in Calibri 11\nGRID_ROWS, ROW_HEIGHT = 40, 15           # points; 20 px each\nMARGIN = 1                               # column A and row 1 are a margin\n\ndef set_up_grid(ws):\n    ws.column_dimensions[\"A\"].width = 2\n    ws.row_dimensions[1].height = 8\n    for c in range(MARGIN + 1, MARGIN + GRID_COLS + 1):\n        ws.column_dimensions[get_column_letter(c)].width = COL_WIDTH\n    for r in range(MARGIN + 1, MARGIN + GRID_ROWS + 1):\n        ws.row_dimensions[r].height = ROW_HEIGHT\n    ws.sheet_view.showGridLines = False\n    ws.sheet_view.zoomScale = 90\n\ndef px(width_chars):                       # Excel's conversion for the default font\n    return int(width_chars * 7 + 5)\n\nprint(\"grid column:\", px(COL_WIDTH), \"px; full grid width:\", GRID_COLS * px(COL_WIDTH), \"px\")\n","python",[14,79,80,96,109,116,144,168,182,187,200,218,235,276,287,322,333,344,355,360,374,400,405],{"__ignoreMap":40},[44,81,82,86,90,93],{"class":46,"line":47},[44,83,85],{"class":84},"s-kum","from",[44,87,89],{"class":88},"skGVy"," openpyxl ",[44,91,92],{"class":84},"import",[44,94,95],{"class":88}," Workbook\n",[44,97,99,101,104,106],{"class":46,"line":98},2,[44,100,85],{"class":84},[44,102,103],{"class":88}," openpyxl.utils ",[44,105,92],{"class":84},[44,107,108],{"class":88}," get_column_letter\n",[44,110,112],{"class":46,"line":111},3,[44,113,115],{"emptyLinePlaceholder":114},true,"\n",[44,117,119,123,126,129,132,135,137,140],{"class":46,"line":118},4,[44,120,122],{"class":121},"sP0c6","GRID_COLS",[44,124,125],{"class":88},", ",[44,127,128],{"class":121},"COL_WIDTH",[44,130,131],{"class":84}," =",[44,133,134],{"class":121}," 24",[44,136,125],{"class":88},[44,138,139],{"class":121},"4.7",[44,141,143],{"class":142},"s-wDw","          # character units; about 37 px each in Calibri 11\n",[44,145,147,150,152,155,157,160,162,165],{"class":46,"line":146},5,[44,148,149],{"class":121},"GRID_ROWS",[44,151,125],{"class":88},[44,153,154],{"class":121},"ROW_HEIGHT",[44,156,131],{"class":84},[44,158,159],{"class":121}," 40",[44,161,125],{"class":88},[44,163,164],{"class":121},"15",[44,166,167],{"class":142},"           # points; 20 px each\n",[44,169,171,174,176,179],{"class":46,"line":170},6,[44,172,173],{"class":121},"MARGIN",[44,175,131],{"class":84},[44,177,178],{"class":121}," 1",[44,180,181],{"class":142},"                               # column A and row 1 are a margin\n",[44,183,185],{"class":46,"line":184},7,[44,186,115],{"emptyLinePlaceholder":114},[44,188,190,193,197],{"class":46,"line":189},8,[44,191,192],{"class":84},"def",[44,194,196],{"class":195},"s_Opv"," set_up_grid",[44,198,199],{"class":88},"(ws):\n",[44,201,203,206,209,212,215],{"class":46,"line":202},9,[44,204,205],{"class":88},"    ws.column_dimensions[",[44,207,208],{"class":54},"\"A\"",[44,210,211],{"class":88},"].width ",[44,213,214],{"class":84},"=",[44,216,217],{"class":121}," 2\n",[44,219,221,224,227,230,232],{"class":46,"line":220},10,[44,222,223],{"class":88},"    ws.row_dimensions[",[44,225,226],{"class":121},"1",[44,228,229],{"class":88},"].height ",[44,231,214],{"class":84},[44,233,234],{"class":121}," 8\n",[44,236,238,241,244,247,250,253,255,258,260,262,264,266,269,271,273],{"class":46,"line":237},11,[44,239,240],{"class":84},"    for",[44,242,243],{"class":88}," c ",[44,245,246],{"class":84},"in",[44,248,249],{"class":121}," range",[44,251,252],{"class":88},"(",[44,254,173],{"class":121},[44,256,257],{"class":84}," +",[44,259,178],{"class":121},[44,261,125],{"class":88},[44,263,173],{"class":121},[44,265,257],{"class":84},[44,267,268],{"class":121}," GRID_COLS",[44,270,257],{"class":84},[44,272,178],{"class":121},[44,274,275],{"class":88},"):\n",[44,277,279,282,284],{"class":46,"line":278},12,[44,280,281],{"class":88},"        ws.column_dimensions[get_column_letter(c)].width ",[44,283,214],{"class":84},[44,285,286],{"class":121}," COL_WIDTH\n",[44,288,290,292,295,297,299,301,303,305,307,309,311,313,316,318,320],{"class":46,"line":289},13,[44,291,240],{"class":84},[44,293,294],{"class":88}," r ",[44,296,246],{"class":84},[44,298,249],{"class":121},[44,300,252],{"class":88},[44,302,173],{"class":121},[44,304,257],{"class":84},[44,306,178],{"class":121},[44,308,125],{"class":88},[44,310,173],{"class":121},[44,312,257],{"class":84},[44,314,315],{"class":121}," GRID_ROWS",[44,317,257],{"class":84},[44,319,178],{"class":121},[44,321,275],{"class":88},[44,323,325,328,330],{"class":46,"line":324},14,[44,326,327],{"class":88},"        ws.row_dimensions[r].height ",[44,329,214],{"class":84},[44,331,332],{"class":121}," ROW_HEIGHT\n",[44,334,336,339,341],{"class":46,"line":335},15,[44,337,338],{"class":88},"    ws.sheet_view.showGridLines ",[44,340,214],{"class":84},[44,342,343],{"class":121}," False\n",[44,345,347,350,352],{"class":46,"line":346},16,[44,348,349],{"class":88},"    ws.sheet_view.zoomScale ",[44,351,214],{"class":84},[44,353,354],{"class":121}," 90\n",[44,356,358],{"class":46,"line":357},17,[44,359,115],{"emptyLinePlaceholder":114},[44,361,363,365,368,371],{"class":46,"line":362},18,[44,364,192],{"class":84},[44,366,367],{"class":195}," px",[44,369,370],{"class":88},"(width_chars):                       ",[44,372,373],{"class":142},"# Excel's conversion for the default font\n",[44,375,377,380,383,386,389,392,394,397],{"class":46,"line":376},19,[44,378,379],{"class":84},"    return",[44,381,382],{"class":121}," int",[44,384,385],{"class":88},"(width_chars ",[44,387,388],{"class":84},"*",[44,390,391],{"class":121}," 7",[44,393,257],{"class":84},[44,395,396],{"class":121}," 5",[44,398,399],{"class":88},")\n",[44,401,403],{"class":46,"line":402},20,[44,404,115],{"emptyLinePlaceholder":114},[44,406,408,411,413,416,419,421,424,427,429,431,434,437,439,441,444],{"class":46,"line":407},21,[44,409,410],{"class":121},"print",[44,412,252],{"class":88},[44,414,415],{"class":54},"\"grid column:\"",[44,417,418],{"class":88},", px(",[44,420,128],{"class":121},[44,422,423],{"class":88},"), ",[44,425,426],{"class":54},"\"px; full grid width:\"",[44,428,125],{"class":88},[44,430,122],{"class":121},[44,432,433],{"class":84}," *",[44,435,436],{"class":88}," px(",[44,438,128],{"class":121},[44,440,423],{"class":88},[44,442,443],{"class":54},"\"px\"",[44,445,399],{"class":88},[10,447,448,449,28],{},"Column widths in Excel are measured in characters of the default font, not pixels, which is why \"the same width\" has to mean the same number in every column rather than an attempt to compute pixels per element. With 24 columns of 37 pixels each the grid is just under 900 pixels wide — comfortable on a laptop at 90% zoom. Rows are measured in points; 15 points is 20 pixels at standard resolution. Turning off gridlines removes the visual noise, and the grid remains in force invisibly. Column widths themselves are covered in ",[24,450,452],{"href":451},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl\u002F","Set Column Width and Row Height in openpyxl",[30,454,456],{"id":455},"step-2-describe-the-layout-in-grid-units","Step 2 — Describe the layout in grid units",[10,458,459],{},"Rather than scattering cell addresses through the code, write the layout as data: each element has a column, row, width and height in grid units. A helper turns that into cell ranges and checks that nothing overlaps:",[35,461,463],{"className":75,"code":462,"language":77,"meta":40,"style":40},"from dataclasses import dataclass\n\n@dataclass\nclass Box:\n    name: str\n    col: int      # 0-based grid column\n    row: int      # 0-based grid row\n    w: int        # width in grid columns\n    h: int        # height in grid rows\n\n    def cells(self):\n        c1, r1 = MARGIN + 1 + self.col, MARGIN + 1 + self.row\n        return c1, r1, c1 + self.w - 1, r1 + self.h - 1\n\n    def ref(self):\n        c1, r1, c2, r2 = self.cells()\n        return f\"{get_column_letter(c1)}{r1}:{get_column_letter(c2)}{r2}\"\n\nLAYOUT = [\n    Box(\"title\", 0, 0, 24, 2),\n    Box(\"kpi_revenue\", 0, 3, 6, 4), Box(\"kpi_orders\", 6, 3, 6, 4), Box(\"kpi_margin\", 12, 3, 6, 4), Box(\"kpi_returns\", 18, 3, 6, 4),\n    Box(\"chart_trend\", 0, 8, 16, 14), Box(\"chart_mix\", 16, 8, 8, 14),\n    Box(\"table_top\", 0, 23, 24, 12),\n]\n\ndef check(layout):\n    taken = {}\n    for b in layout:\n        assert b.col + b.w \u003C= GRID_COLS and b.row + b.h \u003C= GRID_ROWS, f\"{b.name} leaves the grid\"\n        for c in range(b.col, b.col + b.w):\n            for r in range(b.row, b.row + b.h):\n                assert (c, r) not in taken, f\"{b.name} overlaps {taken[(c, r)]}\"\n                taken[(c, r)] = b.name\n    return len(taken)\n\nprint(\"cells used:\", check(LAYOUT), \"of\", GRID_COLS * GRID_ROWS)\nfor b in LAYOUT[:3] + LAYOUT[5:7]:\n    print(f\"  {b.name:\u003C12} {b.ref()}\")\n",[14,464,465,477,481,486,497,505,516,526,536,546,550,561,596,632,636,645,657,701,705,715,745,839,889,916,922,927,938,949,962,1013,1033,1053,1093,1104,1115,1120,1150,1189],{"__ignoreMap":40},[44,466,467,469,472,474],{"class":46,"line":47},[44,468,85],{"class":84},[44,470,471],{"class":88}," dataclasses ",[44,473,92],{"class":84},[44,475,476],{"class":88}," dataclass\n",[44,478,479],{"class":46,"line":98},[44,480,115],{"emptyLinePlaceholder":114},[44,482,483],{"class":46,"line":111},[44,484,485],{"class":195},"@dataclass\n",[44,487,488,491,494],{"class":46,"line":118},[44,489,490],{"class":84},"class",[44,492,493],{"class":50}," Box",[44,495,496],{"class":88},":\n",[44,498,499,502],{"class":46,"line":146},[44,500,501],{"class":88},"    name: ",[44,503,504],{"class":121},"str\n",[44,506,507,510,513],{"class":46,"line":170},[44,508,509],{"class":88},"    col: ",[44,511,512],{"class":121},"int",[44,514,515],{"class":142},"      # 0-based grid column\n",[44,517,518,521,523],{"class":46,"line":184},[44,519,520],{"class":88},"    row: ",[44,522,512],{"class":121},[44,524,525],{"class":142},"      # 0-based grid row\n",[44,527,528,531,533],{"class":46,"line":189},[44,529,530],{"class":88},"    w: ",[44,532,512],{"class":121},[44,534,535],{"class":142},"        # width in grid columns\n",[44,537,538,541,543],{"class":46,"line":202},[44,539,540],{"class":88},"    h: ",[44,542,512],{"class":121},[44,544,545],{"class":142},"        # height in grid rows\n",[44,547,548],{"class":46,"line":220},[44,549,115],{"emptyLinePlaceholder":114},[44,551,552,555,558],{"class":46,"line":237},[44,553,554],{"class":84},"    def",[44,556,557],{"class":195}," cells",[44,559,560],{"class":88},"(self):\n",[44,562,563,566,568,571,573,575,577,580,583,585,587,589,591,593],{"class":46,"line":278},[44,564,565],{"class":88},"        c1, r1 ",[44,567,214],{"class":84},[44,569,570],{"class":121}," MARGIN",[44,572,257],{"class":84},[44,574,178],{"class":121},[44,576,257],{"class":84},[44,578,579],{"class":121}," self",[44,581,582],{"class":88},".col, ",[44,584,173],{"class":121},[44,586,257],{"class":84},[44,588,178],{"class":121},[44,590,257],{"class":84},[44,592,579],{"class":121},[44,594,595],{"class":88},".row\n",[44,597,598,601,604,607,609,612,615,617,620,622,624,627,629],{"class":46,"line":289},[44,599,600],{"class":84},"        return",[44,602,603],{"class":88}," c1, r1, c1 ",[44,605,606],{"class":84},"+",[44,608,579],{"class":121},[44,610,611],{"class":88},".w ",[44,613,614],{"class":84},"-",[44,616,178],{"class":121},[44,618,619],{"class":88},", r1 ",[44,621,606],{"class":84},[44,623,579],{"class":121},[44,625,626],{"class":88},".h ",[44,628,614],{"class":84},[44,630,631],{"class":121}," 1\n",[44,633,634],{"class":46,"line":324},[44,635,115],{"emptyLinePlaceholder":114},[44,637,638,640,643],{"class":46,"line":335},[44,639,554],{"class":84},[44,641,642],{"class":195}," ref",[44,644,560],{"class":88},[44,646,647,650,652,654],{"class":46,"line":346},[44,648,649],{"class":88},"        c1, r1, c2, r2 ",[44,651,214],{"class":84},[44,653,579],{"class":121},[44,655,656],{"class":88},".cells()\n",[44,658,659,661,664,667,671,674,677,680,683,686,688,691,693,696,698],{"class":46,"line":357},[44,660,600],{"class":84},[44,662,663],{"class":84}," f",[44,665,666],{"class":54},"\"",[44,668,670],{"class":669},"sSjpA","{",[44,672,673],{"class":88},"get_column_letter(c1)",[44,675,676],{"class":669},"}{",[44,678,679],{"class":88},"r1",[44,681,682],{"class":669},"}",[44,684,685],{"class":54},":",[44,687,670],{"class":669},[44,689,690],{"class":88},"get_column_letter(c2)",[44,692,676],{"class":669},[44,694,695],{"class":88},"r2",[44,697,682],{"class":669},[44,699,700],{"class":54},"\"\n",[44,702,703],{"class":46,"line":362},[44,704,115],{"emptyLinePlaceholder":114},[44,706,707,710,712],{"class":46,"line":376},[44,708,709],{"class":121},"LAYOUT",[44,711,131],{"class":84},[44,713,714],{"class":88}," [\n",[44,716,717,720,723,725,728,730,732,734,737,739,742],{"class":46,"line":402},[44,718,719],{"class":88},"    Box(",[44,721,722],{"class":54},"\"title\"",[44,724,125],{"class":88},[44,726,727],{"class":121},"0",[44,729,125],{"class":88},[44,731,727],{"class":121},[44,733,125],{"class":88},[44,735,736],{"class":121},"24",[44,738,125],{"class":88},[44,740,741],{"class":121},"2",[44,743,744],{"class":88},"),\n",[44,746,747,749,752,754,756,758,761,763,766,768,771,774,777,779,781,783,785,787,789,791,793,795,798,800,803,805,807,809,811,813,815,817,820,822,825,827,829,831,833,835,837],{"class":46,"line":407},[44,748,719],{"class":88},[44,750,751],{"class":54},"\"kpi_revenue\"",[44,753,125],{"class":88},[44,755,727],{"class":121},[44,757,125],{"class":88},[44,759,760],{"class":121},"3",[44,762,125],{"class":88},[44,764,765],{"class":121},"6",[44,767,125],{"class":88},[44,769,770],{"class":121},"4",[44,772,773],{"class":88},"), Box(",[44,775,776],{"class":54},"\"kpi_orders\"",[44,778,125],{"class":88},[44,780,765],{"class":121},[44,782,125],{"class":88},[44,784,760],{"class":121},[44,786,125],{"class":88},[44,788,765],{"class":121},[44,790,125],{"class":88},[44,792,770],{"class":121},[44,794,773],{"class":88},[44,796,797],{"class":54},"\"kpi_margin\"",[44,799,125],{"class":88},[44,801,802],{"class":121},"12",[44,804,125],{"class":88},[44,806,760],{"class":121},[44,808,125],{"class":88},[44,810,765],{"class":121},[44,812,125],{"class":88},[44,814,770],{"class":121},[44,816,773],{"class":88},[44,818,819],{"class":54},"\"kpi_returns\"",[44,821,125],{"class":88},[44,823,824],{"class":121},"18",[44,826,125],{"class":88},[44,828,760],{"class":121},[44,830,125],{"class":88},[44,832,765],{"class":121},[44,834,125],{"class":88},[44,836,770],{"class":121},[44,838,744],{"class":88},[44,840,842,844,847,849,851,853,856,858,861,863,866,868,871,873,875,877,879,881,883,885,887],{"class":46,"line":841},22,[44,843,719],{"class":88},[44,845,846],{"class":54},"\"chart_trend\"",[44,848,125],{"class":88},[44,850,727],{"class":121},[44,852,125],{"class":88},[44,854,855],{"class":121},"8",[44,857,125],{"class":88},[44,859,860],{"class":121},"16",[44,862,125],{"class":88},[44,864,865],{"class":121},"14",[44,867,773],{"class":88},[44,869,870],{"class":54},"\"chart_mix\"",[44,872,125],{"class":88},[44,874,860],{"class":121},[44,876,125],{"class":88},[44,878,855],{"class":121},[44,880,125],{"class":88},[44,882,855],{"class":121},[44,884,125],{"class":88},[44,886,865],{"class":121},[44,888,744],{"class":88},[44,890,892,894,897,899,901,903,906,908,910,912,914],{"class":46,"line":891},23,[44,893,719],{"class":88},[44,895,896],{"class":54},"\"table_top\"",[44,898,125],{"class":88},[44,900,727],{"class":121},[44,902,125],{"class":88},[44,904,905],{"class":121},"23",[44,907,125],{"class":88},[44,909,736],{"class":121},[44,911,125],{"class":88},[44,913,802],{"class":121},[44,915,744],{"class":88},[44,917,919],{"class":46,"line":918},24,[44,920,921],{"class":88},"]\n",[44,923,925],{"class":46,"line":924},25,[44,926,115],{"emptyLinePlaceholder":114},[44,928,930,932,935],{"class":46,"line":929},26,[44,931,192],{"class":84},[44,933,934],{"class":195}," check",[44,936,937],{"class":88},"(layout):\n",[44,939,941,944,946],{"class":46,"line":940},27,[44,942,943],{"class":88},"    taken ",[44,945,214],{"class":84},[44,947,948],{"class":88}," {}\n",[44,950,952,954,957,959],{"class":46,"line":951},28,[44,953,240],{"class":84},[44,955,956],{"class":88}," b ",[44,958,246],{"class":84},[44,960,961],{"class":88}," layout:\n",[44,963,965,968,971,973,976,979,981,984,987,989,992,994,996,998,1001,1003,1005,1008,1010],{"class":46,"line":964},29,[44,966,967],{"class":84},"        assert",[44,969,970],{"class":88}," b.col ",[44,972,606],{"class":84},[44,974,975],{"class":88}," b.w ",[44,977,978],{"class":84},"\u003C=",[44,980,268],{"class":121},[44,982,983],{"class":84}," and",[44,985,986],{"class":88}," b.row ",[44,988,606],{"class":84},[44,990,991],{"class":88}," b.h ",[44,993,978],{"class":84},[44,995,315],{"class":121},[44,997,125],{"class":88},[44,999,1000],{"class":84},"f",[44,1002,666],{"class":54},[44,1004,670],{"class":669},[44,1006,1007],{"class":88},"b.name",[44,1009,682],{"class":669},[44,1011,1012],{"class":54}," leaves the grid\"\n",[44,1014,1016,1019,1021,1023,1025,1028,1030],{"class":46,"line":1015},30,[44,1017,1018],{"class":84},"        for",[44,1020,243],{"class":88},[44,1022,246],{"class":84},[44,1024,249],{"class":121},[44,1026,1027],{"class":88},"(b.col, b.col ",[44,1029,606],{"class":84},[44,1031,1032],{"class":88}," b.w):\n",[44,1034,1036,1039,1041,1043,1045,1048,1050],{"class":46,"line":1035},31,[44,1037,1038],{"class":84},"            for",[44,1040,294],{"class":88},[44,1042,246],{"class":84},[44,1044,249],{"class":121},[44,1046,1047],{"class":88},"(b.row, b.row ",[44,1049,606],{"class":84},[44,1051,1052],{"class":88}," b.h):\n",[44,1054,1056,1059,1062,1065,1068,1071,1073,1075,1077,1079,1081,1084,1086,1089,1091],{"class":46,"line":1055},32,[44,1057,1058],{"class":84},"                assert",[44,1060,1061],{"class":88}," (c, r) ",[44,1063,1064],{"class":84},"not",[44,1066,1067],{"class":84}," in",[44,1069,1070],{"class":88}," taken, ",[44,1072,1000],{"class":84},[44,1074,666],{"class":54},[44,1076,670],{"class":669},[44,1078,1007],{"class":88},[44,1080,682],{"class":669},[44,1082,1083],{"class":54}," overlaps ",[44,1085,670],{"class":669},[44,1087,1088],{"class":88},"taken[(c, r)]",[44,1090,682],{"class":669},[44,1092,700],{"class":54},[44,1094,1096,1099,1101],{"class":46,"line":1095},33,[44,1097,1098],{"class":88},"                taken[(c, r)] ",[44,1100,214],{"class":84},[44,1102,1103],{"class":88}," b.name\n",[44,1105,1107,1109,1112],{"class":46,"line":1106},34,[44,1108,379],{"class":84},[44,1110,1111],{"class":121}," len",[44,1113,1114],{"class":88},"(taken)\n",[44,1116,1118],{"class":46,"line":1117},35,[44,1119,115],{"emptyLinePlaceholder":114},[44,1121,1123,1125,1127,1130,1133,1135,1137,1140,1142,1144,1146,1148],{"class":46,"line":1122},36,[44,1124,410],{"class":121},[44,1126,252],{"class":88},[44,1128,1129],{"class":54},"\"cells used:\"",[44,1131,1132],{"class":88},", check(",[44,1134,709],{"class":121},[44,1136,423],{"class":88},[44,1138,1139],{"class":54},"\"of\"",[44,1141,125],{"class":88},[44,1143,122],{"class":121},[44,1145,433],{"class":84},[44,1147,315],{"class":121},[44,1149,399],{"class":88},[44,1151,1153,1156,1158,1160,1163,1166,1168,1171,1173,1175,1178,1181,1183,1186],{"class":46,"line":1152},37,[44,1154,1155],{"class":84},"for",[44,1157,956],{"class":88},[44,1159,246],{"class":84},[44,1161,1162],{"class":121}," LAYOUT",[44,1164,1165],{"class":88},"[:",[44,1167,760],{"class":121},[44,1169,1170],{"class":88},"] ",[44,1172,606],{"class":84},[44,1174,1162],{"class":121},[44,1176,1177],{"class":88},"[",[44,1179,1180],{"class":121},"5",[44,1182,685],{"class":88},[44,1184,1185],{"class":121},"7",[44,1187,1188],{"class":88},"]:\n",[44,1190,1192,1195,1197,1199,1202,1204,1206,1209,1211,1214,1217,1219,1221],{"class":46,"line":1191},38,[44,1193,1194],{"class":121},"    print",[44,1196,252],{"class":88},[44,1198,1000],{"class":84},[44,1200,1201],{"class":54},"\"  ",[44,1203,670],{"class":669},[44,1205,1007],{"class":88},[44,1207,1208],{"class":84},":\u003C12",[44,1210,682],{"class":669},[44,1212,1213],{"class":669}," {",[44,1215,1216],{"class":88},"b.ref()",[44,1218,682],{"class":669},[44,1220,666],{"class":54},[44,1222,399],{"class":88},[10,1224,1225,1226,1228],{},"Four KPI tiles of six columns fill the width exactly; the trend chart takes two thirds and the mix chart one third; a table spans the full width below. Changing the design is now a matter of editing numbers in ",[14,1227,709],{},", and the check catches a tile that overlaps a chart or runs off the right edge before the workbook is written — far cheaper than discovering it in Excel.",[1230,1231,1239,1240,1239,1244,1239,1248,1239,1255,1239,1264,1239,1268,1239,1275,1239,1278,1239,1282,1239,1285,1239,1289,1239,1292,1239,1296,1239,1300,1239,1304,1239,1309,1239,1313,1239,1315,1239,1317,1239,1319,1239,1321,1239,1323,1239,1325,1239,1328,1239,1331,1239,1335,1239,1339,1239,1341,1239,1344,1239,1346,1239,1349,1239,1351,1239,1354,1239,1357,1239,1360,1239,1364,1239,1368,1239,1370,1239,1372,1239,1374,1239,1376,1239,1378,1239,1382,1239,1385,1239,1388,1239,1390,1239,1392,1239,1394,1239,1396,1239,1398,1239,1400,1239,1402,1239,1404,1239,1407,1239,1410,1239,1412,1239,1415,1239,1417,1239,1419,1239,1421,1239,1423,1239,1425],"svg",{"viewBox":1232,"role":1233,"ariaLabelledBy":1234,"xmlns":1237,"style":1238},"114 0 532 196","img",[1235,1236],"grid-layout-t","grid-layout-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:532px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[1241,1242,1243],"title",{"id":1235},"The dashboard layout on a 24-column grid",[1245,1246,1247],"desc",{"id":1236},"A title across the full width, four KPI tiles of six columns each, a trend chart over sixteen columns and a mix chart over eight, and a table across the full width underneath. Every edge falls on a grid column boundary.",[1249,1250],"rect",{"x":1251,"y":727,"width":1252,"height":1253,"fill":1254},"114","532","196","#ffffff",[1249,1256],{"x":1257,"y":1258,"width":1259,"height":1260,"rx":727,"fill":1261,"stroke":1262,"style":1263},"130.2","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[1249,1265],{"x":1266,"y":1258,"width":1267,"height":1260,"rx":727,"fill":1261,"stroke":1262,"style":1263},"162.2","116.9",[1269,1270,1274],"text",{"x":1271,"y":1272,"style":1273},"220.7","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[1249,1276],{"x":1277,"y":1258,"width":1267,"height":1260,"rx":727,"fill":1261,"stroke":1262,"style":1263},"279.1",[1269,1279,1281],{"x":1280,"y":1272,"style":1273},"337.6","B",[1249,1283],{"x":1284,"y":1258,"width":1267,"height":1260,"rx":727,"fill":1261,"stroke":1262,"style":1263},"396.0",[1269,1286,1288],{"x":1287,"y":1272,"style":1273},"454.4","C",[1249,1290],{"x":1291,"y":1258,"width":1267,"height":1260,"rx":727,"fill":1261,"stroke":1262,"style":1263},"512.9",[1269,1293,1295],{"x":1294,"y":1272,"style":1273},"571.3","D",[1249,1297],{"x":1257,"y":1298,"width":1259,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1263},"40.0","28.0",[1269,1301,226],{"x":1302,"y":1303,"style":1273},"146.2","59.0",[1249,1305],{"x":1266,"y":1298,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[1269,1310,1312],{"x":1271,"y":1303,"style":1311},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Title",[1249,1314],{"x":1277,"y":1298,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1316,1312],{"x":1280,"y":1303,"style":1311},[1249,1318],{"x":1284,"y":1298,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1320,1312],{"x":1287,"y":1303,"style":1311},[1249,1322],{"x":1291,"y":1298,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1324,1312],{"x":1294,"y":1303,"style":1311},[1249,1326],{"x":1257,"y":1327,"width":1259,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1263},"68.0",[1269,1329,741],{"x":1302,"y":1330,"style":1273},"87.0",[1249,1332],{"x":1266,"y":1327,"width":1267,"height":1299,"rx":727,"fill":1333,"stroke":1334,"style":1308},"#fdefd8","var(--gold,#b4740a)",[1269,1336,1338],{"x":1271,"y":1330,"style":1337},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Revenue",[1249,1340],{"x":1277,"y":1327,"width":1267,"height":1299,"rx":727,"fill":1333,"stroke":1334,"style":1308},[1269,1342,1343],{"x":1280,"y":1330,"style":1337},"Orders",[1249,1345],{"x":1284,"y":1327,"width":1267,"height":1299,"rx":727,"fill":1333,"stroke":1334,"style":1308},[1269,1347,1348],{"x":1287,"y":1330,"style":1337},"Margin",[1249,1350],{"x":1291,"y":1327,"width":1267,"height":1299,"rx":727,"fill":1333,"stroke":1334,"style":1308},[1269,1352,1353],{"x":1294,"y":1330,"style":1337},"Returns",[1249,1355],{"x":1257,"y":1356,"width":1259,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1263},"96.0",[1269,1358,760],{"x":1302,"y":1359,"style":1273},"115.0",[1249,1361],{"x":1266,"y":1356,"width":1267,"height":1299,"rx":727,"fill":1362,"stroke":1363,"style":1308},"#d9f4f1","var(--teal,#0f9488)",[1269,1365,1367],{"x":1271,"y":1359,"style":1366},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Trend chart",[1249,1369],{"x":1277,"y":1356,"width":1267,"height":1299,"rx":727,"fill":1362,"stroke":1363,"style":1308},[1269,1371,1367],{"x":1280,"y":1359,"style":1366},[1249,1373],{"x":1284,"y":1356,"width":1267,"height":1299,"rx":727,"fill":1362,"stroke":1363,"style":1308},[1269,1375,1367],{"x":1287,"y":1359,"style":1366},[1249,1377],{"x":1291,"y":1356,"width":1267,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1308},[1269,1379,1381],{"x":1294,"y":1359,"style":1380},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Mix chart",[1249,1383],{"x":1257,"y":1384,"width":1259,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1263},"124.0",[1269,1386,770],{"x":1302,"y":1387,"style":1273},"143.0",[1249,1389],{"x":1266,"y":1384,"width":1267,"height":1299,"rx":727,"fill":1362,"stroke":1363,"style":1308},[1269,1391,1367],{"x":1271,"y":1387,"style":1366},[1249,1393],{"x":1277,"y":1384,"width":1267,"height":1299,"rx":727,"fill":1362,"stroke":1363,"style":1308},[1269,1395,1367],{"x":1280,"y":1387,"style":1366},[1249,1397],{"x":1284,"y":1384,"width":1267,"height":1299,"rx":727,"fill":1362,"stroke":1363,"style":1308},[1269,1399,1367],{"x":1287,"y":1387,"style":1366},[1249,1401],{"x":1291,"y":1384,"width":1267,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1308},[1269,1403,1381],{"x":1294,"y":1387,"style":1380},[1249,1405],{"x":1257,"y":1406,"width":1259,"height":1299,"rx":727,"fill":1261,"stroke":1262,"style":1263},"152.0",[1269,1408,1180],{"x":1302,"y":1409,"style":1273},"171.0",[1249,1411],{"x":1266,"y":1406,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1413,1414],{"x":1271,"y":1409,"style":1311},"Top customers",[1249,1416],{"x":1277,"y":1406,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1418,1414],{"x":1280,"y":1409,"style":1311},[1249,1420],{"x":1284,"y":1406,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1422,1414],{"x":1287,"y":1409,"style":1311},[1249,1424],{"x":1291,"y":1406,"width":1267,"height":1299,"rx":727,"fill":1306,"stroke":1307,"style":1308},[1269,1426,1414],{"x":1294,"y":1409,"style":1311},[30,1428,1430],{"id":1429},"step-3-draw-tiles-into-their-boxes","Step 3 — Draw tiles into their boxes",[10,1432,1433],{},"Tiles are merged cell ranges with a label and a formula. Because the box gives the range, the drawing code needs no addresses of its own:",[35,1435,1437],{"className":75,"code":1436,"language":77,"meta":40,"style":40},"from openpyxl.styles import Alignment, Border, Font, PatternFill, Side\n\nedge = Side(style=\"thin\", color=\"D0D7DE\")\n\ndef draw_tile(ws, box, label, formula, fmt):\n    c1, r1, c2, r2 = box.cells()\n    ws.merge_cells(start_row=r1, start_column=c1, end_row=r1, end_column=c2)\n    ws.merge_cells(start_row=r1 + 1, start_column=c1, end_row=r2, end_column=c2)\n    ws.cell(r1, c1, label).font = Font(size=9, color=\"57606A\")\n    value = ws.cell(r1 + 1, c1, formula)\n    value.font, value.number_format = Font(size=20, bold=True), fmt\n    for r in range(r1, r2 + 1):\n        for c in range(c1, c2 + 1):\n            cell = ws.cell(r, c)\n            cell.fill = PatternFill(\"solid\", fgColor=\"F6F8FA\")\n            cell.alignment = Alignment(horizontal=\"center\", vertical=\"center\")\n            cell.border = Border(top=edge if r == r1 else None, bottom=edge if r == r2 else None,\n                                 left=edge if c == c1 else None, right=edge if c == c2 else None)\n\nwb = Workbook()\nws = wb.active\nws.title = \"Dashboard\"\nset_up_grid(ws)\nboxes = {b.name: b for b in LAYOUT}\nc1, r1, c2, r2 = boxes[\"title\"].cells()\nws.merge_cells(start_row=r1, start_column=c1, end_row=r2, end_column=c2)\nws.cell(r1, c1, \"Sales dashboard — September 2026\").font = Font(size=16, bold=True)\nfor name, label, formula, fmt in [(\"kpi_revenue\", \"Revenue\", 1284500, \"#,##0\"), (\"kpi_orders\", \"Orders\", 3412, \"#,##0\"),\n                                  (\"kpi_margin\", \"Gross margin\", 0.384, \"0.0%\"), (\"kpi_returns\", \"Return rate\", 0.021, \"0.0%\")]:\n    draw_tile(ws, boxes[name], label, formula, fmt)\nprint(\"merged ranges:\", len(ws.merged_cells.ranges))\n",[14,1438,1439,1451,1455,1486,1490,1500,1510,1546,1582,1611,1628,1657,1676,1695,1705,1730,1759,1818,1864,1868,1878,1888,1898,1903,1924,1939,1968,1999,2049,2092,2097],{"__ignoreMap":40},[44,1440,1441,1443,1446,1448],{"class":46,"line":47},[44,1442,85],{"class":84},[44,1444,1445],{"class":88}," openpyxl.styles ",[44,1447,92],{"class":84},[44,1449,1450],{"class":88}," Alignment, Border, Font, PatternFill, Side\n",[44,1452,1453],{"class":46,"line":98},[44,1454,115],{"emptyLinePlaceholder":114},[44,1456,1457,1460,1462,1465,1469,1471,1474,1476,1479,1481,1484],{"class":46,"line":111},[44,1458,1459],{"class":88},"edge ",[44,1461,214],{"class":84},[44,1463,1464],{"class":88}," Side(",[44,1466,1468],{"class":1467},"sa561","style",[44,1470,214],{"class":84},[44,1472,1473],{"class":54},"\"thin\"",[44,1475,125],{"class":88},[44,1477,1478],{"class":1467},"color",[44,1480,214],{"class":84},[44,1482,1483],{"class":54},"\"D0D7DE\"",[44,1485,399],{"class":88},[44,1487,1488],{"class":46,"line":118},[44,1489,115],{"emptyLinePlaceholder":114},[44,1491,1492,1494,1497],{"class":46,"line":146},[44,1493,192],{"class":84},[44,1495,1496],{"class":195}," draw_tile",[44,1498,1499],{"class":88},"(ws, box, label, formula, fmt):\n",[44,1501,1502,1505,1507],{"class":46,"line":170},[44,1503,1504],{"class":88},"    c1, r1, c2, r2 ",[44,1506,214],{"class":84},[44,1508,1509],{"class":88}," box.cells()\n",[44,1511,1512,1515,1518,1520,1523,1526,1528,1531,1534,1536,1538,1541,1543],{"class":46,"line":184},[44,1513,1514],{"class":88},"    ws.merge_cells(",[44,1516,1517],{"class":1467},"start_row",[44,1519,214],{"class":84},[44,1521,1522],{"class":88},"r1, ",[44,1524,1525],{"class":1467},"start_column",[44,1527,214],{"class":84},[44,1529,1530],{"class":88},"c1, ",[44,1532,1533],{"class":1467},"end_row",[44,1535,214],{"class":84},[44,1537,1522],{"class":88},[44,1539,1540],{"class":1467},"end_column",[44,1542,214],{"class":84},[44,1544,1545],{"class":88},"c2)\n",[44,1547,1548,1550,1552,1554,1557,1559,1561,1563,1565,1567,1569,1571,1573,1576,1578,1580],{"class":46,"line":189},[44,1549,1514],{"class":88},[44,1551,1517],{"class":1467},[44,1553,214],{"class":84},[44,1555,1556],{"class":88},"r1 ",[44,1558,606],{"class":84},[44,1560,178],{"class":121},[44,1562,125],{"class":88},[44,1564,1525],{"class":1467},[44,1566,214],{"class":84},[44,1568,1530],{"class":88},[44,1570,1533],{"class":1467},[44,1572,214],{"class":84},[44,1574,1575],{"class":88},"r2, ",[44,1577,1540],{"class":1467},[44,1579,214],{"class":84},[44,1581,1545],{"class":88},[44,1583,1584,1587,1589,1592,1595,1597,1600,1602,1604,1606,1609],{"class":46,"line":202},[44,1585,1586],{"class":88},"    ws.cell(r1, c1, label).font ",[44,1588,214],{"class":84},[44,1590,1591],{"class":88}," Font(",[44,1593,1594],{"class":1467},"size",[44,1596,214],{"class":84},[44,1598,1599],{"class":121},"9",[44,1601,125],{"class":88},[44,1603,1478],{"class":1467},[44,1605,214],{"class":84},[44,1607,1608],{"class":54},"\"57606A\"",[44,1610,399],{"class":88},[44,1612,1613,1616,1618,1621,1623,1625],{"class":46,"line":220},[44,1614,1615],{"class":88},"    value ",[44,1617,214],{"class":84},[44,1619,1620],{"class":88}," ws.cell(r1 ",[44,1622,606],{"class":84},[44,1624,178],{"class":121},[44,1626,1627],{"class":88},", c1, formula)\n",[44,1629,1630,1633,1635,1637,1639,1641,1644,1646,1649,1651,1654],{"class":46,"line":237},[44,1631,1632],{"class":88},"    value.font, value.number_format ",[44,1634,214],{"class":84},[44,1636,1591],{"class":88},[44,1638,1594],{"class":1467},[44,1640,214],{"class":84},[44,1642,1643],{"class":121},"20",[44,1645,125],{"class":88},[44,1647,1648],{"class":1467},"bold",[44,1650,214],{"class":84},[44,1652,1653],{"class":121},"True",[44,1655,1656],{"class":88},"), fmt\n",[44,1658,1659,1661,1663,1665,1667,1670,1672,1674],{"class":46,"line":278},[44,1660,240],{"class":84},[44,1662,294],{"class":88},[44,1664,246],{"class":84},[44,1666,249],{"class":121},[44,1668,1669],{"class":88},"(r1, r2 ",[44,1671,606],{"class":84},[44,1673,178],{"class":121},[44,1675,275],{"class":88},[44,1677,1678,1680,1682,1684,1686,1689,1691,1693],{"class":46,"line":289},[44,1679,1018],{"class":84},[44,1681,243],{"class":88},[44,1683,246],{"class":84},[44,1685,249],{"class":121},[44,1687,1688],{"class":88},"(c1, c2 ",[44,1690,606],{"class":84},[44,1692,178],{"class":121},[44,1694,275],{"class":88},[44,1696,1697,1700,1702],{"class":46,"line":324},[44,1698,1699],{"class":88},"            cell ",[44,1701,214],{"class":84},[44,1703,1704],{"class":88}," ws.cell(r, c)\n",[44,1706,1707,1710,1712,1715,1718,1720,1723,1725,1728],{"class":46,"line":335},[44,1708,1709],{"class":88},"            cell.fill ",[44,1711,214],{"class":84},[44,1713,1714],{"class":88}," PatternFill(",[44,1716,1717],{"class":54},"\"solid\"",[44,1719,125],{"class":88},[44,1721,1722],{"class":1467},"fgColor",[44,1724,214],{"class":84},[44,1726,1727],{"class":54},"\"F6F8FA\"",[44,1729,399],{"class":88},[44,1731,1732,1735,1737,1740,1743,1745,1748,1750,1753,1755,1757],{"class":46,"line":346},[44,1733,1734],{"class":88},"            cell.alignment ",[44,1736,214],{"class":84},[44,1738,1739],{"class":88}," Alignment(",[44,1741,1742],{"class":1467},"horizontal",[44,1744,214],{"class":84},[44,1746,1747],{"class":54},"\"center\"",[44,1749,125],{"class":88},[44,1751,1752],{"class":1467},"vertical",[44,1754,214],{"class":84},[44,1756,1747],{"class":54},[44,1758,399],{"class":88},[44,1760,1761,1764,1766,1769,1772,1774,1776,1779,1781,1784,1787,1790,1793,1795,1798,1800,1802,1804,1806,1808,1811,1813,1815],{"class":46,"line":357},[44,1762,1763],{"class":88},"            cell.border ",[44,1765,214],{"class":84},[44,1767,1768],{"class":88}," Border(",[44,1770,1771],{"class":1467},"top",[44,1773,214],{"class":84},[44,1775,1459],{"class":88},[44,1777,1778],{"class":84},"if",[44,1780,294],{"class":88},[44,1782,1783],{"class":84},"==",[44,1785,1786],{"class":88}," r1 ",[44,1788,1789],{"class":84},"else",[44,1791,1792],{"class":121}," None",[44,1794,125],{"class":88},[44,1796,1797],{"class":1467},"bottom",[44,1799,214],{"class":84},[44,1801,1459],{"class":88},[44,1803,1778],{"class":84},[44,1805,294],{"class":88},[44,1807,1783],{"class":84},[44,1809,1810],{"class":88}," r2 ",[44,1812,1789],{"class":84},[44,1814,1792],{"class":121},[44,1816,1817],{"class":88},",\n",[44,1819,1820,1823,1825,1827,1829,1831,1833,1836,1838,1840,1842,1845,1847,1849,1851,1853,1855,1858,1860,1862],{"class":46,"line":362},[44,1821,1822],{"class":1467},"                                 left",[44,1824,214],{"class":84},[44,1826,1459],{"class":88},[44,1828,1778],{"class":84},[44,1830,243],{"class":88},[44,1832,1783],{"class":84},[44,1834,1835],{"class":88}," c1 ",[44,1837,1789],{"class":84},[44,1839,1792],{"class":121},[44,1841,125],{"class":88},[44,1843,1844],{"class":1467},"right",[44,1846,214],{"class":84},[44,1848,1459],{"class":88},[44,1850,1778],{"class":84},[44,1852,243],{"class":88},[44,1854,1783],{"class":84},[44,1856,1857],{"class":88}," c2 ",[44,1859,1789],{"class":84},[44,1861,1792],{"class":121},[44,1863,399],{"class":88},[44,1865,1866],{"class":46,"line":376},[44,1867,115],{"emptyLinePlaceholder":114},[44,1869,1870,1873,1875],{"class":46,"line":402},[44,1871,1872],{"class":88},"wb ",[44,1874,214],{"class":84},[44,1876,1877],{"class":88}," Workbook()\n",[44,1879,1880,1883,1885],{"class":46,"line":407},[44,1881,1882],{"class":88},"ws ",[44,1884,214],{"class":84},[44,1886,1887],{"class":88}," wb.active\n",[44,1889,1890,1893,1895],{"class":46,"line":841},[44,1891,1892],{"class":88},"ws.title ",[44,1894,214],{"class":84},[44,1896,1897],{"class":54}," \"Dashboard\"\n",[44,1899,1900],{"class":46,"line":891},[44,1901,1902],{"class":88},"set_up_grid(ws)\n",[44,1904,1905,1908,1910,1913,1915,1917,1919,1921],{"class":46,"line":918},[44,1906,1907],{"class":88},"boxes ",[44,1909,214],{"class":84},[44,1911,1912],{"class":88}," {b.name: b ",[44,1914,1155],{"class":84},[44,1916,956],{"class":88},[44,1918,246],{"class":84},[44,1920,1162],{"class":121},[44,1922,1923],{"class":88},"}\n",[44,1925,1926,1929,1931,1934,1936],{"class":46,"line":924},[44,1927,1928],{"class":88},"c1, r1, c2, r2 ",[44,1930,214],{"class":84},[44,1932,1933],{"class":88}," boxes[",[44,1935,722],{"class":54},[44,1937,1938],{"class":88},"].cells()\n",[44,1940,1941,1944,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964,1966],{"class":46,"line":929},[44,1942,1943],{"class":88},"ws.merge_cells(",[44,1945,1517],{"class":1467},[44,1947,214],{"class":84},[44,1949,1522],{"class":88},[44,1951,1525],{"class":1467},[44,1953,214],{"class":84},[44,1955,1530],{"class":88},[44,1957,1533],{"class":1467},[44,1959,214],{"class":84},[44,1961,1575],{"class":88},[44,1963,1540],{"class":1467},[44,1965,214],{"class":84},[44,1967,1545],{"class":88},[44,1969,1970,1973,1976,1979,1981,1983,1985,1987,1989,1991,1993,1995,1997],{"class":46,"line":940},[44,1971,1972],{"class":88},"ws.cell(r1, c1, ",[44,1974,1975],{"class":54},"\"Sales dashboard — September 2026\"",[44,1977,1978],{"class":88},").font ",[44,1980,214],{"class":84},[44,1982,1591],{"class":88},[44,1984,1594],{"class":1467},[44,1986,214],{"class":84},[44,1988,860],{"class":121},[44,1990,125],{"class":88},[44,1992,1648],{"class":1467},[44,1994,214],{"class":84},[44,1996,1653],{"class":121},[44,1998,399],{"class":88},[44,2000,2001,2003,2006,2008,2011,2013,2015,2018,2020,2023,2025,2028,2031,2033,2035,2038,2040,2043,2045,2047],{"class":46,"line":951},[44,2002,1155],{"class":84},[44,2004,2005],{"class":88}," name, label, formula, fmt ",[44,2007,246],{"class":84},[44,2009,2010],{"class":88}," [(",[44,2012,751],{"class":54},[44,2014,125],{"class":88},[44,2016,2017],{"class":54},"\"Revenue\"",[44,2019,125],{"class":88},[44,2021,2022],{"class":121},"1284500",[44,2024,125],{"class":88},[44,2026,2027],{"class":54},"\"#,##0\"",[44,2029,2030],{"class":88},"), (",[44,2032,776],{"class":54},[44,2034,125],{"class":88},[44,2036,2037],{"class":54},"\"Orders\"",[44,2039,125],{"class":88},[44,2041,2042],{"class":121},"3412",[44,2044,125],{"class":88},[44,2046,2027],{"class":54},[44,2048,744],{"class":88},[44,2050,2051,2054,2056,2058,2061,2063,2066,2068,2071,2073,2075,2077,2080,2082,2085,2087,2089],{"class":46,"line":964},[44,2052,2053],{"class":88},"                                  (",[44,2055,797],{"class":54},[44,2057,125],{"class":88},[44,2059,2060],{"class":54},"\"Gross margin\"",[44,2062,125],{"class":88},[44,2064,2065],{"class":121},"0.384",[44,2067,125],{"class":88},[44,2069,2070],{"class":54},"\"0.0%\"",[44,2072,2030],{"class":88},[44,2074,819],{"class":54},[44,2076,125],{"class":88},[44,2078,2079],{"class":54},"\"Return rate\"",[44,2081,125],{"class":88},[44,2083,2084],{"class":121},"0.021",[44,2086,125],{"class":88},[44,2088,2070],{"class":54},[44,2090,2091],{"class":88},")]:\n",[44,2093,2094],{"class":46,"line":1015},[44,2095,2096],{"class":88},"    draw_tile(ws, boxes[name], label, formula, fmt)\n",[44,2098,2099,2101,2103,2106,2108,2111],{"class":46,"line":1035},[44,2100,410],{"class":121},[44,2102,252],{"class":88},[44,2104,2105],{"class":54},"\"merged ranges:\"",[44,2107,125],{"class":88},[44,2109,2110],{"class":121},"len",[44,2112,2113],{"class":88},"(ws.merged_cells.ranges))\n",[10,2115,2116,2117,2121],{},"Borders are drawn only on a tile's outer edge, so the merged block reads as one card. In a real dashboard the values are formulas pointing at the data sheets, as in ",[24,2118,2120],{"href":2119},"\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","; numbers are used here to keep the example short.",[30,2123,2125],{"id":2124},"step-4-snap-charts-to-the-grid-with-two-cell-anchors","Step 4 — Snap charts to the grid with two-cell anchors",[10,2127,2128,2129,125,2132,2135,2136,2139],{},"By default openpyxl anchors a chart at one cell and gives it a size in centimetres (",[14,2130,2131],{},"chart.width",[14,2133,2134],{},"chart.height","), so its right and bottom edges land wherever that size happens to fall — rarely on a cell boundary. A ",[19,2137,2138],{},"two-cell anchor"," fixes both corners to cells instead:",[35,2141,2143],{"className":75,"code":2142,"language":77,"meta":40,"style":40},"from openpyxl.chart import BarChart, LineChart, Reference\nfrom openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor\n\ndata = wb.create_sheet(\"Data\")\nfor row in [[\"Month\", \"Revenue\", \"Share\"], [\"Jul\", 1180, 0.42], [\"Aug\", 1215, 0.31], [\"Sep\", 1284, 0.27]]:\n    data.append(row)\n\ndef anchor_to(box):\n    c1, r1, c2, r2 = box.cells()\n    anchor = TwoCellAnchor(editAs=\"twoCell\")\n    anchor._from = AnchorMarker(col=c1 - 1, row=r1 - 1)          # markers are 0-based\n    anchor.to = AnchorMarker(col=c2, row=r2)                     # the cell after the box's last cell\n    return anchor\n\ntrend = LineChart()\ntrend.add_data(Reference(data, min_col=2, min_row=1, max_row=4), titles_from_data=True)\ntrend.set_categories(Reference(data, min_col=1, min_row=2, max_row=4))\nmix = BarChart()\nmix.add_data(Reference(data, min_col=3, min_row=1, max_row=4), titles_from_data=True)\nmix.set_categories(Reference(data, min_col=1, min_row=2, max_row=4))\nfor chart, name in [(trend, \"chart_trend\"), (mix, \"chart_mix\")]:\n    chart.x_axis.delete = chart.y_axis.delete = False\n    chart.anchor = anchor_to(boxes[name])\n    ws.add_chart(chart)\nwb.save(\"grid_dashboard.xlsx\")\n",[14,2144,2145,2157,2169,2173,2188,2261,2266,2270,2280,2288,2308,2349,2375,2382,2386,2396,2437,2467,2477,2514,2543,2564,2578,2588,2593],{"__ignoreMap":40},[44,2146,2147,2149,2152,2154],{"class":46,"line":47},[44,2148,85],{"class":84},[44,2150,2151],{"class":88}," openpyxl.chart ",[44,2153,92],{"class":84},[44,2155,2156],{"class":88}," BarChart, LineChart, Reference\n",[44,2158,2159,2161,2164,2166],{"class":46,"line":98},[44,2160,85],{"class":84},[44,2162,2163],{"class":88}," openpyxl.drawing.spreadsheet_drawing ",[44,2165,92],{"class":84},[44,2167,2168],{"class":88}," AnchorMarker, TwoCellAnchor\n",[44,2170,2171],{"class":46,"line":111},[44,2172,115],{"emptyLinePlaceholder":114},[44,2174,2175,2178,2180,2183,2186],{"class":46,"line":118},[44,2176,2177],{"class":88},"data ",[44,2179,214],{"class":84},[44,2181,2182],{"class":88}," wb.create_sheet(",[44,2184,2185],{"class":54},"\"Data\"",[44,2187,399],{"class":88},[44,2189,2190,2192,2195,2197,2200,2203,2205,2207,2209,2212,2215,2218,2220,2223,2225,2228,2230,2233,2235,2238,2240,2243,2245,2248,2250,2253,2255,2258],{"class":46,"line":146},[44,2191,1155],{"class":84},[44,2193,2194],{"class":88}," row ",[44,2196,246],{"class":84},[44,2198,2199],{"class":88}," [[",[44,2201,2202],{"class":54},"\"Month\"",[44,2204,125],{"class":88},[44,2206,2017],{"class":54},[44,2208,125],{"class":88},[44,2210,2211],{"class":54},"\"Share\"",[44,2213,2214],{"class":88},"], [",[44,2216,2217],{"class":54},"\"Jul\"",[44,2219,125],{"class":88},[44,2221,2222],{"class":121},"1180",[44,2224,125],{"class":88},[44,2226,2227],{"class":121},"0.42",[44,2229,2214],{"class":88},[44,2231,2232],{"class":54},"\"Aug\"",[44,2234,125],{"class":88},[44,2236,2237],{"class":121},"1215",[44,2239,125],{"class":88},[44,2241,2242],{"class":121},"0.31",[44,2244,2214],{"class":88},[44,2246,2247],{"class":54},"\"Sep\"",[44,2249,125],{"class":88},[44,2251,2252],{"class":121},"1284",[44,2254,125],{"class":88},[44,2256,2257],{"class":121},"0.27",[44,2259,2260],{"class":88},"]]:\n",[44,2262,2263],{"class":46,"line":170},[44,2264,2265],{"class":88},"    data.append(row)\n",[44,2267,2268],{"class":46,"line":184},[44,2269,115],{"emptyLinePlaceholder":114},[44,2271,2272,2274,2277],{"class":46,"line":189},[44,2273,192],{"class":84},[44,2275,2276],{"class":195}," anchor_to",[44,2278,2279],{"class":88},"(box):\n",[44,2281,2282,2284,2286],{"class":46,"line":202},[44,2283,1504],{"class":88},[44,2285,214],{"class":84},[44,2287,1509],{"class":88},[44,2289,2290,2293,2295,2298,2301,2303,2306],{"class":46,"line":220},[44,2291,2292],{"class":88},"    anchor ",[44,2294,214],{"class":84},[44,2296,2297],{"class":88}," TwoCellAnchor(",[44,2299,2300],{"class":1467},"editAs",[44,2302,214],{"class":84},[44,2304,2305],{"class":54},"\"twoCell\"",[44,2307,399],{"class":88},[44,2309,2310,2313,2315,2318,2321,2323,2326,2328,2330,2332,2335,2337,2339,2341,2343,2346],{"class":46,"line":237},[44,2311,2312],{"class":88},"    anchor._from ",[44,2314,214],{"class":84},[44,2316,2317],{"class":88}," AnchorMarker(",[44,2319,2320],{"class":1467},"col",[44,2322,214],{"class":84},[44,2324,2325],{"class":88},"c1 ",[44,2327,614],{"class":84},[44,2329,178],{"class":121},[44,2331,125],{"class":88},[44,2333,2334],{"class":1467},"row",[44,2336,214],{"class":84},[44,2338,1556],{"class":88},[44,2340,614],{"class":84},[44,2342,178],{"class":121},[44,2344,2345],{"class":88},")          ",[44,2347,2348],{"class":142},"# markers are 0-based\n",[44,2350,2351,2354,2356,2358,2360,2362,2365,2367,2369,2372],{"class":46,"line":278},[44,2352,2353],{"class":88},"    anchor.to ",[44,2355,214],{"class":84},[44,2357,2317],{"class":88},[44,2359,2320],{"class":1467},[44,2361,214],{"class":84},[44,2363,2364],{"class":88},"c2, ",[44,2366,2334],{"class":1467},[44,2368,214],{"class":84},[44,2370,2371],{"class":88},"r2)                     ",[44,2373,2374],{"class":142},"# the cell after the box's last cell\n",[44,2376,2377,2379],{"class":46,"line":289},[44,2378,379],{"class":84},[44,2380,2381],{"class":88}," anchor\n",[44,2383,2384],{"class":46,"line":324},[44,2385,115],{"emptyLinePlaceholder":114},[44,2387,2388,2391,2393],{"class":46,"line":335},[44,2389,2390],{"class":88},"trend ",[44,2392,214],{"class":84},[44,2394,2395],{"class":88}," LineChart()\n",[44,2397,2398,2401,2404,2406,2408,2410,2413,2415,2417,2419,2422,2424,2426,2428,2431,2433,2435],{"class":46,"line":346},[44,2399,2400],{"class":88},"trend.add_data(Reference(data, ",[44,2402,2403],{"class":1467},"min_col",[44,2405,214],{"class":84},[44,2407,741],{"class":121},[44,2409,125],{"class":88},[44,2411,2412],{"class":1467},"min_row",[44,2414,214],{"class":84},[44,2416,226],{"class":121},[44,2418,125],{"class":88},[44,2420,2421],{"class":1467},"max_row",[44,2423,214],{"class":84},[44,2425,770],{"class":121},[44,2427,423],{"class":88},[44,2429,2430],{"class":1467},"titles_from_data",[44,2432,214],{"class":84},[44,2434,1653],{"class":121},[44,2436,399],{"class":88},[44,2438,2439,2442,2444,2446,2448,2450,2452,2454,2456,2458,2460,2462,2464],{"class":46,"line":357},[44,2440,2441],{"class":88},"trend.set_categories(Reference(data, ",[44,2443,2403],{"class":1467},[44,2445,214],{"class":84},[44,2447,226],{"class":121},[44,2449,125],{"class":88},[44,2451,2412],{"class":1467},[44,2453,214],{"class":84},[44,2455,741],{"class":121},[44,2457,125],{"class":88},[44,2459,2421],{"class":1467},[44,2461,214],{"class":84},[44,2463,770],{"class":121},[44,2465,2466],{"class":88},"))\n",[44,2468,2469,2472,2474],{"class":46,"line":362},[44,2470,2471],{"class":88},"mix ",[44,2473,214],{"class":84},[44,2475,2476],{"class":88}," BarChart()\n",[44,2478,2479,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2502,2504,2506,2508,2510,2512],{"class":46,"line":376},[44,2480,2481],{"class":88},"mix.add_data(Reference(data, ",[44,2483,2403],{"class":1467},[44,2485,214],{"class":84},[44,2487,760],{"class":121},[44,2489,125],{"class":88},[44,2491,2412],{"class":1467},[44,2493,214],{"class":84},[44,2495,226],{"class":121},[44,2497,125],{"class":88},[44,2499,2421],{"class":1467},[44,2501,214],{"class":84},[44,2503,770],{"class":121},[44,2505,423],{"class":88},[44,2507,2430],{"class":1467},[44,2509,214],{"class":84},[44,2511,1653],{"class":121},[44,2513,399],{"class":88},[44,2515,2516,2519,2521,2523,2525,2527,2529,2531,2533,2535,2537,2539,2541],{"class":46,"line":402},[44,2517,2518],{"class":88},"mix.set_categories(Reference(data, ",[44,2520,2403],{"class":1467},[44,2522,214],{"class":84},[44,2524,226],{"class":121},[44,2526,125],{"class":88},[44,2528,2412],{"class":1467},[44,2530,214],{"class":84},[44,2532,741],{"class":121},[44,2534,125],{"class":88},[44,2536,2421],{"class":1467},[44,2538,214],{"class":84},[44,2540,770],{"class":121},[44,2542,2466],{"class":88},[44,2544,2545,2547,2550,2552,2555,2557,2560,2562],{"class":46,"line":407},[44,2546,1155],{"class":84},[44,2548,2549],{"class":88}," chart, name ",[44,2551,246],{"class":84},[44,2553,2554],{"class":88}," [(trend, ",[44,2556,846],{"class":54},[44,2558,2559],{"class":88},"), (mix, ",[44,2561,870],{"class":54},[44,2563,2091],{"class":88},[44,2565,2566,2569,2571,2574,2576],{"class":46,"line":841},[44,2567,2568],{"class":88},"    chart.x_axis.delete ",[44,2570,214],{"class":84},[44,2572,2573],{"class":88}," chart.y_axis.delete ",[44,2575,214],{"class":84},[44,2577,343],{"class":121},[44,2579,2580,2583,2585],{"class":46,"line":891},[44,2581,2582],{"class":88},"    chart.anchor ",[44,2584,214],{"class":84},[44,2586,2587],{"class":88}," anchor_to(boxes[name])\n",[44,2589,2590],{"class":46,"line":918},[44,2591,2592],{"class":88},"    ws.add_chart(chart)\n",[44,2594,2595,2598,2601],{"class":46,"line":924},[44,2596,2597],{"class":88},"wb.save(",[44,2599,2600],{"class":54},"\"grid_dashboard.xlsx\"",[44,2602,399],{"class":88},[10,2604,2605,2606,2608,2609,2612,2613,2616,2617,2620],{},"The ",[14,2607,85],{}," marker is the top-left cell and the ",[14,2610,2611],{},"to"," marker is the cell just past the bottom-right one, both counted from zero, so the chart covers exactly the box's cells. ",[14,2614,2615],{},"editAs=\"twoCell\""," tells Excel to move ",[19,2618,2619],{},"and resize"," the chart with its cells: widen a grid column and the chart widens with it, keeping every edge aligned with the tiles above. The trend chart's right edge and the mix chart's left edge now share a cell boundary, so there is neither a gap nor an overlap between them.",[1230,2622,1239,2628,1239,2631,1239,2634,1239,2638,1239,2644,1239,2655,1239,2661,1239,2666,1239,2671,1239,2675,1239,2679,1239,2683,1239,2686,1239,2693,1239,2698,1239,2702,1239,2705,1239,2708,1239,2711],{"viewBox":2623,"role":1233,"ariaLabelledBy":2624,"xmlns":1237,"style":2627},"4 0 752 233",[2625,2626],"grid-anchor-t","grid-anchor-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[1241,2629,2630],{"id":2625},"One-cell anchor or two-cell anchor",[1245,2632,2633],{"id":2626},"A one-cell anchor fixes the chart's top-left corner to a cell and sets its size in centimetres, so its other edges fall between cells. A two-cell anchor fixes both corners to cells, so all four edges line up with the grid and the chart resizes with its columns.",[1249,2635],{"x":770,"y":727,"width":2636,"height":2637,"fill":1254},"752","233",[1249,2639],{"x":2640,"y":1258,"width":2641,"height":2642,"rx":865,"fill":1333,"stroke":1334,"style":2643},"20.0","351.0","201.0","stroke-width:2px",[2645,2646,2648],"g",{"transform":2647},"translate(182.5,29.0) scale(1.083)",[2649,2650],"path",{"d":2651,"fill":2652,"stroke":2653,"style":2654},"M4 5h16v14H4z M4 16l5-5 4 4 3-3 4 4 M15 9h.01","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[1269,2656,2660],{"x":2657,"y":2658,"style":2659},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","One-cell anchor (default)",[46,2662],{"x1":2663,"y1":2664,"x2":2665,"y2":2664,"stroke":1334,"style":1263},"36.0","91.0","355.0",[1269,2667,2670],{"x":2657,"y":2668,"style":2669},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","top-left on a cell",[1269,2672,2674],{"x":2657,"y":2673,"style":2669},"137.0","size in cm",[1269,2676,2678],{"x":2657,"y":2677,"style":2669},"163.0","edges fall between cells",[1269,2680,2682],{"x":2657,"y":2681,"style":2669},"189.0","does not follow column widths",[1249,2684],{"x":2685,"y":1258,"width":2641,"height":2642,"rx":865,"fill":1362,"stroke":1363,"style":2643},"389.0",[2645,2687,2689],{"transform":2688},"translate(551.5,29.0) scale(1.083)",[2649,2690],{"d":2691,"fill":2652,"stroke":2692,"style":2654},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[1269,2694,2697],{"x":2695,"y":2658,"style":2696},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Two-cell anchor",[46,2699],{"x1":2700,"y1":2664,"x2":2701,"y2":2664,"stroke":1363,"style":1263},"405.0","724.0",[1269,2703,2704],{"x":2695,"y":2668,"style":2669},"both corners on cells",[1269,2706,2707],{"x":2695,"y":2673,"style":2669},"size comes from the grid",[1269,2709,2710],{"x":2695,"y":2677,"style":2669},"edges align with tiles",[1269,2712,2713],{"x":2695,"y":2681,"style":2669},"resizes with its cells",[30,2715,2717],{"id":2716},"step-5-check-the-saved-anchors","Step 5 — Check the saved anchors",[10,2719,2720],{},"openpyxl does not read charts back when it loads a workbook, but the anchors are plain XML in the drawing part, so they can be checked directly:",[35,2722,2724],{"className":75,"code":2723,"language":77,"meta":40,"style":40},"import re\nimport zipfile\n\nwith zipfile.ZipFile(\"grid_dashboard.xlsx\") as z:\n    drawing = z.read(\"xl\u002Fdrawings\u002Fdrawing1.xml\").decode()\nanchors = re.findall(r\"\u003C(?:xdr:)?twoCellAnchor.*?\u003C(?:xdr:)?from>\u003C(?:xdr:)?col>(\\d+)\u003C\u002F(?:xdr:)?col>.*?\u003C(?:xdr:)?row>(\\d+)\u003C\u002F(?:xdr:)?row>\"\n                     r\".*?\u003C(?:xdr:)?to>\u003C(?:xdr:)?col>(\\d+)\u003C\u002F(?:xdr:)?col>.*?\u003C(?:xdr:)?row>(\\d+)\u003C\u002F(?:xdr:)?row>\", drawing, re.S)\nfor (fc, fr, tc, tr), name in zip(anchors, [\"chart_trend\", \"chart_mix\"]):\n    print(f\"{name:\u003C12} from col {fc} row {fr} to col {tc} row {tr}  -> cells {boxes[name].ref()}\")\n",[14,2725,2726,2733,2740,2744,2763,2779,2896,2986,3010],{"__ignoreMap":40},[44,2727,2728,2730],{"class":46,"line":47},[44,2729,92],{"class":84},[44,2731,2732],{"class":88}," re\n",[44,2734,2735,2737],{"class":46,"line":98},[44,2736,92],{"class":84},[44,2738,2739],{"class":88}," zipfile\n",[44,2741,2742],{"class":46,"line":111},[44,2743,115],{"emptyLinePlaceholder":114},[44,2745,2746,2749,2752,2754,2757,2760],{"class":46,"line":118},[44,2747,2748],{"class":84},"with",[44,2750,2751],{"class":88}," zipfile.ZipFile(",[44,2753,2600],{"class":54},[44,2755,2756],{"class":88},") ",[44,2758,2759],{"class":84},"as",[44,2761,2762],{"class":88}," z:\n",[44,2764,2765,2768,2770,2773,2776],{"class":46,"line":146},[44,2766,2767],{"class":88},"    drawing ",[44,2769,214],{"class":84},[44,2771,2772],{"class":88}," z.read(",[44,2774,2775],{"class":54},"\"xl\u002Fdrawings\u002Fdrawing1.xml\"",[44,2777,2778],{"class":88},").decode()\n",[44,2780,2781,2784,2786,2789,2792,2795,2798,2801,2804,2807,2810,2812,2815,2818,2820,2822,2824,2826,2829,2831,2833,2835,2837,2840,2843,2845,2847,2850,2852,2854,2856,2858,2860,2862,2864,2866,2868,2870,2872,2874,2877,2879,2881,2883,2885,2887,2889,2891,2893],{"class":46,"line":170},[44,2782,2783],{"class":88},"anchors ",[44,2785,214],{"class":84},[44,2787,2788],{"class":88}," re.findall(",[44,2790,2791],{"class":84},"r",[44,2793,2794],{"class":54},"\"\u003C",[44,2796,2797],{"class":121},"(?:",[44,2799,2800],{"class":54},"xdr:",[44,2802,2803],{"class":121},")",[44,2805,2806],{"class":84},"?",[44,2808,2809],{"class":54},"twoCellAnchor",[44,2811,28],{"class":121},[44,2813,2814],{"class":84},"*?",[44,2816,2817],{"class":54},"\u003C",[44,2819,2797],{"class":121},[44,2821,2800],{"class":54},[44,2823,2803],{"class":121},[44,2825,2806],{"class":84},[44,2827,2828],{"class":54},"from>\u003C",[44,2830,2797],{"class":121},[44,2832,2800],{"class":54},[44,2834,2803],{"class":121},[44,2836,2806],{"class":84},[44,2838,2839],{"class":54},"col>",[44,2841,2842],{"class":121},"(\\d",[44,2844,606],{"class":84},[44,2846,2803],{"class":121},[44,2848,2849],{"class":54},"\u003C\u002F",[44,2851,2797],{"class":121},[44,2853,2800],{"class":54},[44,2855,2803],{"class":121},[44,2857,2806],{"class":84},[44,2859,2839],{"class":54},[44,2861,28],{"class":121},[44,2863,2814],{"class":84},[44,2865,2817],{"class":54},[44,2867,2797],{"class":121},[44,2869,2800],{"class":54},[44,2871,2803],{"class":121},[44,2873,2806],{"class":84},[44,2875,2876],{"class":54},"row>",[44,2878,2842],{"class":121},[44,2880,606],{"class":84},[44,2882,2803],{"class":121},[44,2884,2849],{"class":54},[44,2886,2797],{"class":121},[44,2888,2800],{"class":54},[44,2890,2803],{"class":121},[44,2892,2806],{"class":84},[44,2894,2895],{"class":54},"row>\"\n",[44,2897,2898,2901,2903,2905,2907,2909,2911,2913,2915,2917,2920,2922,2924,2926,2928,2930,2932,2934,2936,2938,2940,2942,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974,2976,2978,2980,2983],{"class":46,"line":184},[44,2899,2900],{"class":84},"                     r",[44,2902,666],{"class":54},[44,2904,28],{"class":121},[44,2906,2814],{"class":84},[44,2908,2817],{"class":54},[44,2910,2797],{"class":121},[44,2912,2800],{"class":54},[44,2914,2803],{"class":121},[44,2916,2806],{"class":84},[44,2918,2919],{"class":54},"to>\u003C",[44,2921,2797],{"class":121},[44,2923,2800],{"class":54},[44,2925,2803],{"class":121},[44,2927,2806],{"class":84},[44,2929,2839],{"class":54},[44,2931,2842],{"class":121},[44,2933,606],{"class":84},[44,2935,2803],{"class":121},[44,2937,2849],{"class":54},[44,2939,2797],{"class":121},[44,2941,2800],{"class":54},[44,2943,2803],{"class":121},[44,2945,2806],{"class":84},[44,2947,2839],{"class":54},[44,2949,28],{"class":121},[44,2951,2814],{"class":84},[44,2953,2817],{"class":54},[44,2955,2797],{"class":121},[44,2957,2800],{"class":54},[44,2959,2803],{"class":121},[44,2961,2806],{"class":84},[44,2963,2876],{"class":54},[44,2965,2842],{"class":121},[44,2967,606],{"class":84},[44,2969,2803],{"class":121},[44,2971,2849],{"class":54},[44,2973,2797],{"class":121},[44,2975,2800],{"class":54},[44,2977,2803],{"class":121},[44,2979,2806],{"class":84},[44,2981,2982],{"class":54},"row>\"",[44,2984,2985],{"class":88},", drawing, re.S)\n",[44,2987,2988,2990,2993,2995,2998,3001,3003,3005,3007],{"class":46,"line":189},[44,2989,1155],{"class":84},[44,2991,2992],{"class":88}," (fc, fr, tc, tr), name ",[44,2994,246],{"class":84},[44,2996,2997],{"class":121}," zip",[44,2999,3000],{"class":88},"(anchors, [",[44,3002,846],{"class":54},[44,3004,125],{"class":88},[44,3006,870],{"class":54},[44,3008,3009],{"class":88},"]):\n",[44,3011,3012,3014,3016,3018,3020,3022,3025,3027,3029,3032,3034,3037,3039,3041,3043,3046,3048,3051,3053,3056,3058,3060,3062,3065,3067,3070,3072,3075,3077,3079],{"class":46,"line":202},[44,3013,1194],{"class":121},[44,3015,252],{"class":88},[44,3017,1000],{"class":84},[44,3019,666],{"class":54},[44,3021,670],{"class":669},[44,3023,3024],{"class":88},"name",[44,3026,1208],{"class":84},[44,3028,682],{"class":669},[44,3030,3031],{"class":54}," from col ",[44,3033,670],{"class":669},[44,3035,3036],{"class":88},"fc",[44,3038,682],{"class":669},[44,3040,2194],{"class":54},[44,3042,670],{"class":669},[44,3044,3045],{"class":88},"fr",[44,3047,682],{"class":669},[44,3049,3050],{"class":54}," to col ",[44,3052,670],{"class":669},[44,3054,3055],{"class":88},"tc",[44,3057,682],{"class":669},[44,3059,2194],{"class":54},[44,3061,670],{"class":669},[44,3063,3064],{"class":88},"tr",[44,3066,682],{"class":669},[44,3068,3069],{"class":54},"  -> cells ",[44,3071,670],{"class":669},[44,3073,3074],{"class":88},"boxes[name].ref()",[44,3076,682],{"class":669},[44,3078,666],{"class":54},[44,3080,399],{"class":88},[10,3082,3083,3084,3086,3087,3089,3090,3094],{},"Both charts start and end exactly on the cells their boxes describe, and the trend chart's ",[14,3085,2611],{}," column is the mix chart's ",[14,3088,85],{}," column — the shared edge. A check like this belongs in the test suite of any report that is supposed to look the same every month; ",[24,3091,3093],{"href":3092},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Ftest-excel-output-with-pytest\u002F","Test Excel Output with pytest"," shows the wider testing approach.",[1230,3096,1239,3101,1239,3104,1239,3107,1239,3110,1239,3114,1239,3121,1239,3127,1239,3132,1239,3136,1239,3140,1239,3145,1239,3148,1239,3155,1239,3160,1239,3164,1239,3167,1239,3171,1239,3174,1239,3177,1239,3183,1239,3188,1239,3191,1239,3194,1239,3198,1239,3201,1239,3204,1239,3210,1239,3216,1239,3219,1239,3222,1239,3225,1239,3229,1239,3233,1239,3236,1239,3239,1239,3245,1239,3250,1239,3253,1239,3257],{"viewBox":3097,"role":1233,"ariaLabelledBy":3098,"xmlns":1237,"style":2627},"4 0 752 178",[3099,3100],"grid-flow-t","grid-flow-d",[1241,3102,3103],{"id":3099},"From layout data to an aligned dashboard",[1245,3105,3106],{"id":3100},"Uniform columns and rows define the grid. The layout is a list of boxes in grid units, checked for overlaps. Tiles are merged over their boxes and charts are given two-cell anchors on the same boxes, then the saved anchors are checked.",[1249,3108],{"x":770,"y":727,"width":2636,"height":3109,"fill":1254},"178",[1249,3111],{"x":2640,"y":1258,"width":3112,"height":3113,"rx":802,"fill":1261,"stroke":1262,"style":2643},"115.2","145.5",[2645,3115,3117],{"transform":3116},"translate(65.6,46.5) scale(1.000)",[2649,3118],{"d":2691,"fill":2652,"stroke":3119,"style":3120},"var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1269,3122,3126],{"x":3123,"y":3124,"style":3125},"77.6","93.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Uniform grid",[1269,3128,3131],{"x":3123,"y":3129,"style":3130},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","equal columns",[1269,3133,3135],{"x":3123,"y":3134,"style":3130},"130.0","and rows",[46,3137],{"x1":3138,"y1":3139,"x2":1266,"y2":3139,"stroke":1307,"style":2643},"140.2","88.8",[3141,3142],"polygon",{"points":3143,"fill":3144},"166.2,88.8 157.2,93.8 157.2,83.8","#5b5cf0",[1249,3146],{"x":3147,"y":1258,"width":3112,"height":3113,"rx":802,"fill":1306,"stroke":1307,"style":2643},"171.2",[2645,3149,3151],{"transform":3150},"translate(216.8,46.5) scale(1.000)",[2649,3152],{"d":3153,"fill":2652,"stroke":3154,"style":3120},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--brand-strong,#4338ca)",[1269,3156,3159],{"x":3157,"y":3124,"style":3158},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Layout as",[1269,3161,3163],{"x":3157,"y":3162,"style":3158},"110.5","data",[1269,3165,3166],{"x":3157,"y":3134,"style":3130},"col, row, w, h",[46,3168],{"x1":3169,"y1":3139,"x2":3170,"y2":3139,"stroke":1307,"style":2643},"291.4","313.4",[3141,3172],{"points":3173,"fill":3144},"317.4,88.8 308.4,93.8 308.4,83.8",[1249,3175],{"x":3176,"y":1258,"width":3112,"height":3113,"rx":802,"fill":1333,"stroke":1334,"style":2643},"322.4",[2645,3178,3180],{"transform":3179},"translate(368.0,46.5) scale(1.000)",[2649,3181],{"d":3182,"fill":2652,"stroke":2653,"style":3120},"M5 12.5l4.5 4.5L19 7",[1269,3184,3187],{"x":3185,"y":3124,"style":3186},"380.0","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Overlap",[1269,3189,3190],{"x":3185,"y":3162,"style":3186},"check",[1269,3192,3193],{"x":3185,"y":3134,"style":3130},"before writing",[46,3195],{"x1":3196,"y1":3139,"x2":3197,"y2":3139,"stroke":1307,"style":2643},"442.6","464.6",[3141,3199],{"points":3200,"fill":3144},"468.6,88.8 459.6,93.8 459.6,83.8",[1249,3202],{"x":3203,"y":1258,"width":3112,"height":3113,"rx":802,"fill":1362,"stroke":1363,"style":2643},"473.6",[2645,3205,3207],{"transform":3206},"translate(519.2,29.0) scale(1.000)",[2649,3208],{"d":3209,"fill":2652,"stroke":2692,"style":3120},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[1269,3211,3215],{"x":3212,"y":3213,"style":3214},"531.2","75.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Tiles and",[1269,3217,3218],{"x":3212,"y":3124,"style":3214},"charts",[1269,3220,3221],{"x":3212,"y":3129,"style":3130},"merged cells,",[1269,3223,3224],{"x":3212,"y":3134,"style":3130},"two-cell",[1269,3226,3228],{"x":3212,"y":3227,"style":3130},"147.5","anchors",[46,3230],{"x1":3231,"y1":3139,"x2":3232,"y2":3139,"stroke":1307,"style":2643},"593.8","615.8",[3141,3234],{"points":3235,"fill":3144},"619.8,88.8 610.8,93.8 610.8,83.8",[1249,3237],{"x":3238,"y":1258,"width":3112,"height":3113,"rx":802,"fill":1261,"stroke":1262,"style":2643},"624.8",[2645,3240,3242],{"transform":3241},"translate(670.4,37.8) scale(1.000)",[2649,3243],{"d":3244,"fill":2652,"stroke":3119,"style":3120},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[1269,3246,3249],{"x":3247,"y":3248,"style":3125},"682.4","84.2","Verify",[1269,3251,3228],{"x":3247,"y":3252,"style":3125},"101.8",[1269,3254,3256],{"x":3247,"y":3255,"style":3130},"121.2","read drawing",[1269,3258,3260],{"x":3247,"y":3259,"style":3130},"138.8","XML",[30,3262,3264],{"id":3263},"common-pitfalls","Common pitfalls",[3266,3267,3268,3276,3282,3294,3300],"ul",{},[3269,3270,3271,3275],"li",{},[3272,3273,3274],"strong",{},"Charts sized in centimetres."," Their edges fall between cells; use two-cell anchors.",[3269,3277,3278,3281],{},[3272,3279,3280],{},"Columns of mixed widths."," Elements cannot share edges unless the grid is uniform.",[3269,3283,3284,3287,3288,3290,3291,3293],{},[3272,3285,3286],{},"Off-by-one markers."," ",[14,3289,85],{}," is the first cell, ",[14,3292,2611],{}," the cell after the last, both 0-based.",[3269,3295,3296,3299],{},[3272,3297,3298],{},"Addresses scattered through code."," Keep the layout in one list and check it.",[3269,3301,3302,3305,3306,28],{},[3272,3303,3304],{},"Charts that do not resize."," Use ",[14,3307,2615],{},[30,3309,3311],{"id":3310},"conclusion","Conclusion",[10,3313,3314],{},"Make the dashboard sheet a uniform grid — equal column widths and row heights, gridlines hidden — and describe every tile and chart as a box in grid units, checked for overlaps before anything is written. Draw tiles as merged ranges over their boxes and give charts two-cell anchors on the same boxes, so every edge lands on a cell boundary and charts resize with their columns. Read the anchors back from the drawing XML to confirm the layout in a test.",[30,3316,3318],{"id":3317},"frequently-asked-questions","Frequently asked questions",[10,3320,3321,3324],{},[3272,3322,3323],{},"How do I align charts exactly with cells in openpyxl?","\nGive the chart a TwoCellAnchor whose from marker is the top-left cell and whose to marker is the cell after the bottom-right cell, both zero-based, and set editAs to twoCell.",[10,3326,3327,3330],{},[3272,3328,3329],{},"Why do my openpyxl charts not line up with each other?","\nBy default a chart is anchored at one cell with a size in centimetres, so its other edges fall between cells. Anchoring both corners to cells on a uniform grid aligns them.",[10,3332,3333,3336],{},[3272,3334,3335],{},"What grid should an Excel dashboard use?","\nA 12- or 24-column grid of equal widths is easy to divide into halves, thirds and quarters, with a narrow first column as a margin.",[10,3338,3339,3342],{},[3272,3340,3341],{},"Can I check chart positions in a saved workbook?","\nopenpyxl does not load charts, but the anchors are in the drawing XML inside the xlsx zip file and can be read with zipfile.",[30,3344,3346],{"id":3345},"related","Related",[3266,3348,3349,3355,3362,3369],{},[3269,3350,3351,3352,3354],{},"Up one level: ",[24,3353,27],{"href":26}," — dashboards from Python.",[3269,3356,3357,3361],{},[24,3358,3360],{"href":3359},"\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"," — the charts themselves.",[3269,3363,3364,3368],{},[24,3365,3367],{"href":3366},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard\u002F","Build a Rolling 12-Month Chart on an Excel Dashboard"," — a chart that follows the data.",[3269,3370,3371,3375],{},[24,3372,3374],{"href":3373},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fmerge-cells-and-centre-a-report-title-with-openpyxl\u002F","Merge Cells and Centre a Report Title with openpyxl"," — merged tiles.",[1468,3377,3378],{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":40,"searchDepth":98,"depth":98,"links":3380},[3381,3382,3383,3384,3385,3386,3387,3388,3389,3390],{"id":32,"depth":98,"text":33},{"id":68,"depth":98,"text":69},{"id":455,"depth":98,"text":456},{"id":1429,"depth":98,"text":1430},{"id":2124,"depth":98,"text":2125},{"id":2716,"depth":98,"text":2717},{"id":3263,"depth":98,"text":3264},{"id":3310,"depth":98,"text":3311},{"id":3317,"depth":98,"text":3318},{"id":3345,"depth":98,"text":3346},"2026-10-02","Lay out a generated Excel dashboard on a regular grid with openpyxl: set uniform column widths and row heights, describe every tile and chart by grid position and span, snap charts to cell boundaries with two-cell anchors so they line up and resize with the grid, and check the layout for overlaps before anyone opens the file.","md",[3395,3397,3399,3401],{"q":3323,"a":3396},"Give the chart a TwoCellAnchor whose from marker is the top-left cell and whose to marker is the cell after the bottom-right cell, both zero-based, and set editAs to twoCell.",{"q":3329,"a":3398},"By default a chart is anchored at one cell with a size in centimetres, so its other edges fall between cells. Anchoring both corners to cells on a uniform grid aligns them.",{"q":3335,"a":3400},"A 12- or 24-column grid of equal widths is easy to divide into halves, thirds and quarters, with a narrow first column as a margin.",{"q":3341,"a":3402},"openpyxl does not load charts, but the anchors are in the drawing XML inside the xlsx zip file and can be read with zipfile.","lay out an excel dashboard sheet on a grid",null,{"breadcrumb":3406},[3407,3410,3413],{"name":3408,"item":3409},"Home","\u002F",{"name":3411,"item":3412},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":27,"item":26},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid",{"title":3416,"description":3417},"Lay Out an Excel Dashboard Sheet on a Grid with openpyxl","Grid layout for Excel dashboards from Python: uniform columns and rows, tiles and charts placed by grid span, TwoCellAnchor to snap charts to cells, overlap checks and reading anchors back from the file.","lay-out-a-dashboard-sheet-on-a-grid","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid\u002Findex",[3421,3422],"report-layouts","charts-and-images","how-to","BTdy-rKbHb1VhId77Llp38zv811FSWUNuUwCzEnuPEM",[3426,3429],{"title":3367,"path":3427,"stem":3428,"children":-1},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-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",{"title":3430,"path":3431,"stem":3432,"children":-1},"Link Dashboard Cells to Data Sheets with Formulas","\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flink-dashboard-cells-to-data-sheets-with-formulas","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flink-dashboard-cells-to-data-sheets-with-formulas\u002Findex",[3434,3436],{"path":3435,"title":27},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards",{"path":3437,"title":3411},"\u002Fautomating-reporting-workflows",[],[3440,3444,3447,3450,3453,3456],{"path":3441,"title":3442,"kind":3443},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports","Building Finance and Operations Reports in Excel with Python","guide",{"path":3445,"title":3446,"kind":3423},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fallocate-shared-costs-across-departments","Allocate Shared Costs Across Departments with Python",{"path":3448,"title":3449,"kind":3423},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report","Build a Bank Reconciliation Report with Python and Excel",{"path":3451,"title":3452,"kind":3423},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bill-of-materials-cost-rollup","Build a Bill of Materials Cost Rollup",{"path":3454,"title":3455,"kind":3423},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python","Build a Break-Even Analysis in Excel with Python",{"path":3457,"title":3458,"kind":3459},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report","Build a Budget vs Actual Variance Report in Excel with Python","compare",1790972129472]