[{"data":1,"prerenderedAt":2113},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl":2080,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl":2089,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl":2094,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl":2095},{"id":4,"title":5,"body":6,"dateModified":2046,"datePublished":2046,"description":2047,"extension":2048,"faq":2049,"keyword":2058,"kind":2059,"meta":2060,"navigation":117,"path":2069,"seo":2070,"slug":2073,"stem":2074,"topics":2075,"type":2078,"__hash__":2079},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl\u002Findex.md","Create One Chart per Group with openpyxl",{"type":7,"value":8,"toc":2033},"minimark",[9,24,29,58,68,72,75,321,328,656,660,1011,1022,1026,1029,1267,1273,1368,1372,1375,1454,1471,1475,1478,1770,1777,1897,1901,1908,1912,1957,1961,1968,1972,1978,1984,1990,1996,2000,2029],[10,11,12,13,17,18,23],"p",{},"One chart with twelve lines on it — one per region — is unreadable; twelve small charts with one line each, arranged in a grid, are easy to scan. That arrangement, often called ",[14,15,16],"em",{},"small multiples",", is a standard way to compare groups, and building it by hand in Excel is exactly the kind of repetitive work a script should do. openpyxl can create any number of native charts in a loop. The work is in three details: each chart must reference only its own group's rows, the charts must be positioned so they do not overlap, and — the one most often missed — every chart must use the same axis scale, or the eye compares shapes that are not comparable. This page belongs to ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," openpyxl",[40,56,57],{"class":50}," pandas\n",[10,59,60,63,64,67],{},[37,61,62],{},"regional_sales.xlsx"," is a long table with one row per region and month: six regions, nine months. LibreOffice (",[37,65,66],{},"soffice",") is used at the end to render the sheet for a quick visual check; it is optional.",[25,69,71],{"id":70},"step-1-write-each-group-as-its-own-block","Step 1 — Write each group as its own block",[10,73,74],{},"A chart references a rectangular range, so each region's data needs to sit in a contiguous block. Pivoting to one column per region is the compact way:",[30,76,80],{"className":77,"code":78,"language":79,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import Workbook\n\nsales = pd.read_excel(\"regional_sales.xlsx\")\nwide = sales.pivot(index=\"Month\", columns=\"Region\", values=\"Sales\")\nregions = list(sales[\"Region\"].unique())\nwide = wide[regions]\n\nwb = Workbook()\nws = wb.active\nws.title = \"Data\"\nws.append([\"Month\", *regions])\nfor month, row in wide.iterrows():\n    ws.append([month, *row.tolist()])\nprint(ws.dimensions, \"|\", [c.value for c in ws[1]])\n","python",[37,81,82,98,112,119,137,180,200,210,215,226,237,248,264,279,290],{"__ignoreMap":35},[40,83,84,88,92,95],{"class":42,"line":43},[40,85,87],{"class":86},"s-kum","import",[40,89,91],{"class":90},"skGVy"," pandas ",[40,93,94],{"class":86},"as",[40,96,97],{"class":90}," pd\n",[40,99,101,104,107,109],{"class":42,"line":100},2,[40,102,103],{"class":86},"from",[40,105,106],{"class":90}," openpyxl ",[40,108,87],{"class":86},[40,110,111],{"class":90}," Workbook\n",[40,113,115],{"class":42,"line":114},3,[40,116,118],{"emptyLinePlaceholder":117},true,"\n",[40,120,122,125,128,131,134],{"class":42,"line":121},4,[40,123,124],{"class":90},"sales ",[40,126,127],{"class":86},"=",[40,129,130],{"class":90}," pd.read_excel(",[40,132,133],{"class":50},"\"regional_sales.xlsx\"",[40,135,136],{"class":90},")\n",[40,138,140,143,145,148,152,154,157,160,163,165,168,170,173,175,178],{"class":42,"line":139},5,[40,141,142],{"class":90},"wide ",[40,144,127],{"class":86},[40,146,147],{"class":90}," sales.pivot(",[40,149,151],{"class":150},"sa561","index",[40,153,127],{"class":86},[40,155,156],{"class":50},"\"Month\"",[40,158,159],{"class":90},", ",[40,161,162],{"class":150},"columns",[40,164,127],{"class":86},[40,166,167],{"class":50},"\"Region\"",[40,169,159],{"class":90},[40,171,172],{"class":150},"values",[40,174,127],{"class":86},[40,176,177],{"class":50},"\"Sales\"",[40,179,136],{"class":90},[40,181,183,186,188,192,195,197],{"class":42,"line":182},6,[40,184,185],{"class":90},"regions ",[40,187,127],{"class":86},[40,189,191],{"class":190},"sP0c6"," list",[40,193,194],{"class":90},"(sales[",[40,196,167],{"class":50},[40,198,199],{"class":90},"].unique())\n",[40,201,203,205,207],{"class":42,"line":202},7,[40,204,142],{"class":90},[40,206,127],{"class":86},[40,208,209],{"class":90}," wide[regions]\n",[40,211,213],{"class":42,"line":212},8,[40,214,118],{"emptyLinePlaceholder":117},[40,216,218,221,223],{"class":42,"line":217},9,[40,219,220],{"class":90},"wb ",[40,222,127],{"class":86},[40,224,225],{"class":90}," Workbook()\n",[40,227,229,232,234],{"class":42,"line":228},10,[40,230,231],{"class":90},"ws ",[40,233,127],{"class":86},[40,235,236],{"class":90}," wb.active\n",[40,238,240,243,245],{"class":42,"line":239},11,[40,241,242],{"class":90},"ws.title ",[40,244,127],{"class":86},[40,246,247],{"class":50}," \"Data\"\n",[40,249,251,254,256,258,261],{"class":42,"line":250},12,[40,252,253],{"class":90},"ws.append([",[40,255,156],{"class":50},[40,257,159],{"class":90},[40,259,260],{"class":86},"*",[40,262,263],{"class":90},"regions])\n",[40,265,267,270,273,276],{"class":42,"line":266},13,[40,268,269],{"class":86},"for",[40,271,272],{"class":90}," month, row ",[40,274,275],{"class":86},"in",[40,277,278],{"class":90}," wide.iterrows():\n",[40,280,282,285,287],{"class":42,"line":281},14,[40,283,284],{"class":90},"    ws.append([month, ",[40,286,260],{"class":86},[40,288,289],{"class":90},"row.tolist()])\n",[40,291,293,296,299,302,305,307,310,312,315,318],{"class":42,"line":292},15,[40,294,295],{"class":190},"print",[40,297,298],{"class":90},"(ws.dimensions, ",[40,300,301],{"class":50},"\"|\"",[40,303,304],{"class":90},", [c.value ",[40,306,269],{"class":86},[40,308,309],{"class":90}," c ",[40,311,275],{"class":86},[40,313,314],{"class":90}," ws[",[40,316,317],{"class":190},"1",[40,319,320],{"class":90},"]])\n",[10,322,323,324,23],{},"The data sheet now has months down column A and one column per region. Each chart will use column A for its categories and one region's column for its values. Keeping the data on its own sheet and the charts on another keeps the dashboard tidy and lets people inspect the numbers. Reshaping long data to wide is covered in ",[19,325,327],{"href":326},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a Pivot Table from Excel with pandas",[329,330,338,339,338,343,338,347,338,355,338,364,338,368,338,375,338,379,338,383,338,387,338,391,338,395,338,398,338,403,338,407,338,409,338,412,338,414,338,417,338,420,338,424,338,426,338,430,338,432,338,435,338,437,338,440,338,443,338,447,338,449,338,451,338,453,338,456,338,458,338,461,338,464,338,468,338,470,338,473,338,475,338,477,338,479,338,482,338,485,338,489,338,491,338,493,338,495,338,497,338,499,338,502,338,505,338,508,338,511,338,515,338,518,338,522,338,525,338,529,338,533,338,535,338,538,338,540,338,542,338,544,338,546,338,548,338,550,338,552,338,555,338,557,338,559,338,561,338,563,338,567,338,570,338,572,338,574,338,576,338,579,338,581,338,583,338,585,338,587,338,589,338,591,338,593,338,595,338,597,338,600,338,602,338,604,338,606,338,609,338,611,338,613,338,615,338,617,338,619,338,621,338,627,338,631,338,637,338,642,338,650],"svg",{"viewBox":331,"role":332,"ariaLabelledBy":333,"xmlns":336,"style":337},"66 0 627 220","img",[334,335],"cg-wide-t","cg-wide-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:627px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[340,341,342],"title",{"id":334},"A long table becomes one column per group",[344,345,346],"desc",{"id":335},"On the left the long table with Region, Month and Sales rows. On the right a wide block with months down the side and one column per region, so each region's values form a contiguous range a chart can reference.",[348,349],"rect",{"x":350,"y":351,"width":352,"height":353,"fill":354},"66","0","627","220","#ffffff",[348,356],{"x":357,"y":358,"width":359,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"83.0","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[348,365],{"x":366,"y":358,"width":367,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"115.0","61.4",[369,370,374],"text",{"x":371,"y":372,"style":373},"145.7","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[348,376],{"x":377,"y":358,"width":378,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"176.4","72.0",[369,380,382],{"x":381,"y":372,"style":373},"212.4","B",[348,384],{"x":385,"y":358,"width":386,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"248.4","53.4",[369,388,390],{"x":389,"y":372,"style":373},"275.1","C",[348,392],{"x":357,"y":393,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},"64.0","28.0",[369,396,317],{"x":397,"y":357,"style":373},"99.0",[348,399],{"x":366,"y":393,"width":367,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[369,404,406],{"x":371,"y":357,"style":405},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Region",[348,408],{"x":377,"y":393,"width":378,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,410,411],{"x":381,"y":357,"style":405},"Month",[348,413],{"x":385,"y":393,"width":386,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,415,416],{"x":389,"y":357,"style":405},"Sales",[348,418],{"x":357,"y":419,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},"92.0",[369,421,423],{"x":397,"y":422,"style":373},"111.0","2",[348,425],{"x":366,"y":419,"width":367,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,427,429],{"x":371,"y":422,"style":428},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[348,431],{"x":377,"y":419,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,433,434],{"x":381,"y":422,"style":428},"2026-01",[348,436],{"x":385,"y":419,"width":386,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,438,439],{"x":389,"y":422,"style":428},"1276",[348,441],{"x":357,"y":442,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},"120.0",[369,444,446],{"x":397,"y":445,"style":373},"139.0","3",[348,448],{"x":366,"y":442,"width":367,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,450,429],{"x":371,"y":445,"style":428},[348,452],{"x":377,"y":442,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,454,455],{"x":381,"y":445,"style":428},"2026-02",[348,457],{"x":385,"y":442,"width":386,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,459,460],{"x":389,"y":445,"style":428},"1376",[348,462],{"x":357,"y":463,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},"148.0",[369,465,467],{"x":397,"y":466,"style":373},"167.0","4",[348,469],{"x":366,"y":463,"width":367,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,471,472],{"x":371,"y":466,"style":428},"South",[348,474],{"x":377,"y":463,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,476,434],{"x":381,"y":466,"style":428},[348,478],{"x":385,"y":463,"width":386,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,480,481],{"x":389,"y":466,"style":428},"930",[348,483],{"x":357,"y":484,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},"176.0",[369,486,488],{"x":397,"y":487,"style":373},"195.0","5",[348,490],{"x":366,"y":484,"width":367,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,492,472],{"x":371,"y":487,"style":428},[348,494],{"x":377,"y":484,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,496,455],{"x":381,"y":487,"style":428},[348,498],{"x":385,"y":484,"width":386,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,500,501],{"x":389,"y":487,"style":428},"946",[348,503],{"x":504,"y":358,"width":359,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"412.2",[348,506],{"x":507,"y":358,"width":378,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"444.2",[369,509,374],{"x":510,"y":372,"style":373},"480.2",[348,512],{"x":513,"y":358,"width":514,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"516.2","54.6",[369,516,382],{"x":517,"y":372,"style":373},"543.5",[348,519],{"x":520,"y":358,"width":521,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"570.7","55.8",[369,523,390],{"x":524,"y":372,"style":373},"598.6",[348,526],{"x":527,"y":358,"width":528,"height":360,"rx":351,"fill":361,"stroke":362,"style":363},"626.5","50.5",[369,530,532],{"x":531,"y":372,"style":373},"651.8","D",[348,534],{"x":504,"y":393,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},[369,536,317],{"x":537,"y":357,"style":373},"428.2",[348,539],{"x":507,"y":393,"width":378,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,541,411],{"x":510,"y":357,"style":405},[348,543],{"x":513,"y":393,"width":514,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,545,429],{"x":517,"y":357,"style":405},[348,547],{"x":520,"y":393,"width":521,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,549,472],{"x":524,"y":357,"style":405},[348,551],{"x":527,"y":393,"width":528,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,553,554],{"x":531,"y":357,"style":405},"East",[348,556],{"x":504,"y":419,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},[369,558,423],{"x":537,"y":422,"style":373},[348,560],{"x":507,"y":419,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,562,434],{"x":510,"y":422,"style":428},[348,564],{"x":513,"y":419,"width":514,"height":394,"rx":351,"fill":565,"stroke":566,"style":402},"#d9f4f1","var(--teal,#0f9488)",[369,568,439],{"x":517,"y":422,"style":569},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[348,571],{"x":520,"y":419,"width":521,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,573,481],{"x":524,"y":422,"style":428},[348,575],{"x":527,"y":419,"width":528,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,577,578],{"x":531,"y":422,"style":428},"1498",[348,580],{"x":504,"y":442,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},[369,582,446],{"x":537,"y":445,"style":373},[348,584],{"x":507,"y":442,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,586,455],{"x":510,"y":445,"style":428},[348,588],{"x":513,"y":442,"width":514,"height":394,"rx":351,"fill":565,"stroke":566,"style":402},[369,590,460],{"x":517,"y":445,"style":569},[348,592],{"x":520,"y":442,"width":521,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,594,501],{"x":524,"y":445,"style":428},[348,596],{"x":527,"y":442,"width":528,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,598,599],{"x":531,"y":445,"style":428},"1685",[348,601],{"x":504,"y":463,"width":359,"height":394,"rx":351,"fill":361,"stroke":362,"style":363},[369,603,467],{"x":537,"y":466,"style":373},[348,605],{"x":507,"y":463,"width":378,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,607,608],{"x":510,"y":466,"style":428},"…",[348,610],{"x":513,"y":463,"width":514,"height":394,"rx":351,"fill":565,"stroke":566,"style":402},[369,612,608],{"x":517,"y":466,"style":569},[348,614],{"x":520,"y":463,"width":521,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,616,608],{"x":524,"y":466,"style":428},[348,618],{"x":527,"y":463,"width":528,"height":394,"rx":351,"fill":354,"stroke":362,"style":363},[369,620,608],{"x":531,"y":466,"style":428},[369,622,626],{"x":623,"y":624,"style":625},"192.4","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","long",[369,628,630],{"x":629,"y":624,"style":625},"544.6","one block per region",[42,632],{"x1":633,"y1":634,"x2":635,"y2":634,"stroke":401,"style":636},"311.7","108.0","398.2","stroke-width:2px",[638,639],"polygon",{"points":640,"fill":641},"402.2,108.0 393.2,113.0 393.2,103.0","#5b5cf0",[348,643],{"x":644,"y":645,"width":646,"height":647,"rx":648,"fill":354,"stroke":362,"style":649},"328.8","75.0","56.3","22.0","11","stroke-width:1.2px",[369,651,655],{"x":652,"y":653,"style":654},"357.0","90.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","pivot",[25,657,659],{"id":658},"step-2-one-chart-per-column-in-a-loop","Step 2 — One chart per column, in a loop",[30,661,663],{"className":77,"code":662,"language":79,"meta":35,"style":35},"from openpyxl.chart import LineChart, Reference\n\nn_months = len(wide)\ny_max = float(wide.max().max()) * 1.1             # one scale for every chart\n\ndef region_chart(col_idx, title):\n    chart = LineChart()\n    chart.title = title\n    chart.add_data(Reference(ws, min_col=col_idx, min_row=1, max_row=n_months + 1), titles_from_data=True)\n    chart.set_categories(Reference(ws, min_col=1, min_row=2, max_row=n_months + 1))\n    chart.legend = None\n    chart.y_axis.scaling.min = 0\n    chart.y_axis.scaling.max = round(y_max, -2)\n    chart.x_axis.delete = False\n    chart.y_axis.delete = False\n    chart.width, chart.height = 9, 5.5\n    for s in chart.series:\n        s.smooth = False\n    return chart\n\ncharts = [region_chart(i, name) for i, name in enumerate(regions, start=2)]\nprint(len(charts), \"charts; shared y-axis max:\", round(y_max, -2))\n",[37,664,665,677,681,694,716,720,732,742,752,800,834,844,854,874,884,893,909,923,933,942,947,981],{"__ignoreMap":35},[40,666,667,669,672,674],{"class":42,"line":43},[40,668,103],{"class":86},[40,670,671],{"class":90}," openpyxl.chart ",[40,673,87],{"class":86},[40,675,676],{"class":90}," LineChart, Reference\n",[40,678,679],{"class":42,"line":100},[40,680,118],{"emptyLinePlaceholder":117},[40,682,683,686,688,691],{"class":42,"line":114},[40,684,685],{"class":90},"n_months ",[40,687,127],{"class":86},[40,689,690],{"class":190}," len",[40,692,693],{"class":90},"(wide)\n",[40,695,696,699,701,704,707,709,712],{"class":42,"line":121},[40,697,698],{"class":90},"y_max ",[40,700,127],{"class":86},[40,702,703],{"class":190}," float",[40,705,706],{"class":90},"(wide.max().max()) ",[40,708,260],{"class":86},[40,710,711],{"class":190}," 1.1",[40,713,715],{"class":714},"s-wDw","             # one scale for every chart\n",[40,717,718],{"class":42,"line":139},[40,719,118],{"emptyLinePlaceholder":117},[40,721,722,725,729],{"class":42,"line":182},[40,723,724],{"class":86},"def",[40,726,728],{"class":727},"s_Opv"," region_chart",[40,730,731],{"class":90},"(col_idx, title):\n",[40,733,734,737,739],{"class":42,"line":202},[40,735,736],{"class":90},"    chart ",[40,738,127],{"class":86},[40,740,741],{"class":90}," LineChart()\n",[40,743,744,747,749],{"class":42,"line":212},[40,745,746],{"class":90},"    chart.title ",[40,748,127],{"class":86},[40,750,751],{"class":90}," title\n",[40,753,754,757,760,762,765,768,770,772,774,777,779,781,784,787,790,793,795,798],{"class":42,"line":217},[40,755,756],{"class":90},"    chart.add_data(Reference(ws, ",[40,758,759],{"class":150},"min_col",[40,761,127],{"class":86},[40,763,764],{"class":90},"col_idx, ",[40,766,767],{"class":150},"min_row",[40,769,127],{"class":86},[40,771,317],{"class":190},[40,773,159],{"class":90},[40,775,776],{"class":150},"max_row",[40,778,127],{"class":86},[40,780,685],{"class":90},[40,782,783],{"class":86},"+",[40,785,786],{"class":190}," 1",[40,788,789],{"class":90},"), ",[40,791,792],{"class":150},"titles_from_data",[40,794,127],{"class":86},[40,796,797],{"class":190},"True",[40,799,136],{"class":90},[40,801,802,805,807,809,811,813,815,817,819,821,823,825,827,829,831],{"class":42,"line":228},[40,803,804],{"class":90},"    chart.set_categories(Reference(ws, ",[40,806,759],{"class":150},[40,808,127],{"class":86},[40,810,317],{"class":190},[40,812,159],{"class":90},[40,814,767],{"class":150},[40,816,127],{"class":86},[40,818,423],{"class":190},[40,820,159],{"class":90},[40,822,776],{"class":150},[40,824,127],{"class":86},[40,826,685],{"class":90},[40,828,783],{"class":86},[40,830,786],{"class":190},[40,832,833],{"class":90},"))\n",[40,835,836,839,841],{"class":42,"line":239},[40,837,838],{"class":90},"    chart.legend ",[40,840,127],{"class":86},[40,842,843],{"class":190}," None\n",[40,845,846,849,851],{"class":42,"line":250},[40,847,848],{"class":90},"    chart.y_axis.scaling.min ",[40,850,127],{"class":86},[40,852,853],{"class":190}," 0\n",[40,855,856,859,861,864,867,870,872],{"class":42,"line":266},[40,857,858],{"class":90},"    chart.y_axis.scaling.max ",[40,860,127],{"class":86},[40,862,863],{"class":190}," round",[40,865,866],{"class":90},"(y_max, ",[40,868,869],{"class":86},"-",[40,871,423],{"class":190},[40,873,136],{"class":90},[40,875,876,879,881],{"class":42,"line":281},[40,877,878],{"class":90},"    chart.x_axis.delete ",[40,880,127],{"class":86},[40,882,883],{"class":190}," False\n",[40,885,886,889,891],{"class":42,"line":292},[40,887,888],{"class":90},"    chart.y_axis.delete ",[40,890,127],{"class":86},[40,892,883],{"class":190},[40,894,896,899,901,904,906],{"class":42,"line":895},16,[40,897,898],{"class":90},"    chart.width, chart.height ",[40,900,127],{"class":86},[40,902,903],{"class":190}," 9",[40,905,159],{"class":90},[40,907,908],{"class":190},"5.5\n",[40,910,912,915,918,920],{"class":42,"line":911},17,[40,913,914],{"class":86},"    for",[40,916,917],{"class":90}," s ",[40,919,275],{"class":86},[40,921,922],{"class":90}," chart.series:\n",[40,924,926,929,931],{"class":42,"line":925},18,[40,927,928],{"class":90},"        s.smooth ",[40,930,127],{"class":86},[40,932,883],{"class":190},[40,934,936,939],{"class":42,"line":935},19,[40,937,938],{"class":86},"    return",[40,940,941],{"class":90}," chart\n",[40,943,945],{"class":42,"line":944},20,[40,946,118],{"emptyLinePlaceholder":117},[40,948,950,953,955,958,960,963,965,968,971,974,976,978],{"class":42,"line":949},21,[40,951,952],{"class":90},"charts ",[40,954,127],{"class":86},[40,956,957],{"class":90}," [region_chart(i, name) ",[40,959,269],{"class":86},[40,961,962],{"class":90}," i, name ",[40,964,275],{"class":86},[40,966,967],{"class":190}," enumerate",[40,969,970],{"class":90},"(regions, ",[40,972,973],{"class":150},"start",[40,975,127],{"class":86},[40,977,423],{"class":190},[40,979,980],{"class":90},")]\n",[40,982,984,986,989,992,995,998,1000,1003,1005,1007,1009],{"class":42,"line":983},22,[40,985,295],{"class":190},[40,987,988],{"class":90},"(",[40,990,991],{"class":190},"len",[40,993,994],{"class":90},"(charts), ",[40,996,997],{"class":50},"\"charts; shared y-axis max:\"",[40,999,159],{"class":90},[40,1001,1002],{"class":190},"round",[40,1004,866],{"class":90},[40,1006,869],{"class":86},[40,1008,423],{"class":190},[40,1010,833],{"class":90},[10,1012,1013,1014,1017,1018,23],{},"Each chart takes one region's column (from row 1, so the header becomes the series name) and the shared month column as categories. The legend is dropped because the title already names the single series. The two lines that matter most are the shared axis limits: every chart runs from 0 to the same maximum, rounded to the nearest hundred. Without them, Excel scales each chart to its own data, and West's flat line at 600 would look as dramatic as East's line at 1,500 — the most common way small multiples mislead. ",[37,1015,1016],{},"smooth = False"," keeps openpyxl's line series from being drawn as curves, a default noted in ",[19,1019,1021],{"href":1020},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-line-chart-to-excel-report-with-python\u002F","Add a Line Chart to an Excel Report with Python",[25,1023,1025],{"id":1024},"step-3-lay-the-charts-out-in-a-grid","Step 3 — Lay the charts out in a grid",[10,1027,1028],{},"Charts are anchored to a cell. Compute the anchor for each position in a grid of, say, three columns:",[30,1030,1032],{"className":77,"code":1031,"language":79,"meta":35,"style":35},"from openpyxl.utils import get_column_letter\n\ndash = wb.create_sheet(\"Charts\", 0)\ndash[\"A1\"] = \"Sales by region, January–September 2026 (same scale on every chart)\"\nCOLS, CELL_W, CELL_H = 3, 6, 12                   # charts per row; columns and rows per chart slot\nfor i, chart in enumerate(charts):\n    row_slot, col_slot = divmod(i, COLS)\n    anchor = f\"{get_column_letter(1 + col_slot * CELL_W)}{3 + row_slot * CELL_H}\"\n    dash.add_chart(chart, anchor)\n    print(f\"{regions[i]:8} -> {anchor}\")\nwb.active = 0\nwb.save(\"regional_small_multiples.xlsx\")\n",[37,1033,1034,1046,1050,1069,1085,1119,1133,1150,1207,1212,1248,1257],{"__ignoreMap":35},[40,1035,1036,1038,1041,1043],{"class":42,"line":43},[40,1037,103],{"class":86},[40,1039,1040],{"class":90}," openpyxl.utils ",[40,1042,87],{"class":86},[40,1044,1045],{"class":90}," get_column_letter\n",[40,1047,1048],{"class":42,"line":100},[40,1049,118],{"emptyLinePlaceholder":117},[40,1051,1052,1055,1057,1060,1063,1065,1067],{"class":42,"line":114},[40,1053,1054],{"class":90},"dash ",[40,1056,127],{"class":86},[40,1058,1059],{"class":90}," wb.create_sheet(",[40,1061,1062],{"class":50},"\"Charts\"",[40,1064,159],{"class":90},[40,1066,351],{"class":190},[40,1068,136],{"class":90},[40,1070,1071,1074,1077,1080,1082],{"class":42,"line":121},[40,1072,1073],{"class":90},"dash[",[40,1075,1076],{"class":50},"\"A1\"",[40,1078,1079],{"class":90},"] ",[40,1081,127],{"class":86},[40,1083,1084],{"class":50}," \"Sales by region, January–September 2026 (same scale on every chart)\"\n",[40,1086,1087,1090,1092,1095,1097,1100,1103,1106,1108,1111,1113,1116],{"class":42,"line":139},[40,1088,1089],{"class":190},"COLS",[40,1091,159],{"class":90},[40,1093,1094],{"class":190},"CELL_W",[40,1096,159],{"class":90},[40,1098,1099],{"class":190},"CELL_H",[40,1101,1102],{"class":86}," =",[40,1104,1105],{"class":190}," 3",[40,1107,159],{"class":90},[40,1109,1110],{"class":190},"6",[40,1112,159],{"class":90},[40,1114,1115],{"class":190},"12",[40,1117,1118],{"class":714},"                   # charts per row; columns and rows per chart slot\n",[40,1120,1121,1123,1126,1128,1130],{"class":42,"line":182},[40,1122,269],{"class":86},[40,1124,1125],{"class":90}," i, chart ",[40,1127,275],{"class":86},[40,1129,967],{"class":190},[40,1131,1132],{"class":90},"(charts):\n",[40,1134,1135,1138,1140,1143,1146,1148],{"class":42,"line":202},[40,1136,1137],{"class":90},"    row_slot, col_slot ",[40,1139,127],{"class":86},[40,1141,1142],{"class":190}," divmod",[40,1144,1145],{"class":90},"(i, ",[40,1147,1089],{"class":190},[40,1149,136],{"class":90},[40,1151,1152,1155,1157,1160,1163,1167,1170,1172,1175,1178,1180,1183,1186,1189,1191,1193,1196,1198,1201,1204],{"class":42,"line":212},[40,1153,1154],{"class":90},"    anchor ",[40,1156,127],{"class":86},[40,1158,1159],{"class":86}," f",[40,1161,1162],{"class":50},"\"",[40,1164,1166],{"class":1165},"sSjpA","{",[40,1168,1169],{"class":90},"get_column_letter(",[40,1171,317],{"class":190},[40,1173,1174],{"class":86}," +",[40,1176,1177],{"class":90}," col_slot ",[40,1179,260],{"class":86},[40,1181,1182],{"class":190}," CELL_W",[40,1184,1185],{"class":90},")",[40,1187,1188],{"class":1165},"}{",[40,1190,446],{"class":190},[40,1192,1174],{"class":86},[40,1194,1195],{"class":90}," row_slot ",[40,1197,260],{"class":86},[40,1199,1200],{"class":190}," CELL_H",[40,1202,1203],{"class":1165},"}",[40,1205,1206],{"class":50},"\"\n",[40,1208,1209],{"class":42,"line":217},[40,1210,1211],{"class":90},"    dash.add_chart(chart, anchor)\n",[40,1213,1214,1217,1219,1222,1224,1226,1229,1232,1234,1237,1239,1242,1244,1246],{"class":42,"line":228},[40,1215,1216],{"class":190},"    print",[40,1218,988],{"class":90},[40,1220,1221],{"class":86},"f",[40,1223,1162],{"class":50},[40,1225,1166],{"class":1165},[40,1227,1228],{"class":90},"regions[i]",[40,1230,1231],{"class":86},":8",[40,1233,1203],{"class":1165},[40,1235,1236],{"class":50}," -> ",[40,1238,1166],{"class":1165},[40,1240,1241],{"class":90},"anchor",[40,1243,1203],{"class":1165},[40,1245,1162],{"class":50},[40,1247,136],{"class":90},[40,1249,1250,1253,1255],{"class":42,"line":239},[40,1251,1252],{"class":90},"wb.active ",[40,1254,127],{"class":86},[40,1256,853],{"class":190},[40,1258,1259,1262,1265],{"class":42,"line":250},[40,1260,1261],{"class":90},"wb.save(",[40,1263,1264],{"class":50},"\"regional_small_multiples.xlsx\"",[40,1266,136],{"class":90},[10,1268,1269,1272],{},[37,1270,1271],{},"divmod(i, 3)"," turns the chart number into a grid position — row 0, columns 0–2, then row 1 — and each slot is six columns wide and twelve rows tall, a little more than the chart's own size, so neighbouring charts never overlap. The title row above the grid states that the scales are shared, which tells readers they may compare heights directly. Making the chart sheet the active one means the file opens on the dashboard.",[329,1274,338,1280,338,1283,338,1286,338,1291,338,1296,338,1300,338,1306,338,1310,338,1314,338,1318,338,1322,338,1325,338,1330,338,1332,338,1336,338,1338,338,1341,338,1343,338,1346,338,1348,338,1352,338,1354,338,1358,338,1360,338,1363,338,1365],{"viewBox":1275,"role":332,"ariaLabelledBy":1276,"xmlns":336,"style":1279},"189 0 381 116",[1277,1278],"cg-grid-t","cg-grid-d","width:100%;max-width:381px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[340,1281,1282],{"id":1277},"Chart anchors in a three-column grid",[344,1284,1285],{"id":1278},"The Charts sheet with six chart slots. North is anchored at A3, South at G3 and East at M3 in the first row; West at A15, Central at G15 and Coast at M15 in the second row. Each slot is six columns wide and twelve rows tall.",[348,1287],{"x":1288,"y":351,"width":1289,"height":1290,"fill":354},"189","381","116",[348,1292],{"x":1293,"y":1294,"width":1295,"height":394,"rx":351,"fill":361,"stroke":362,"style":402},"205.9","16.0","79.8",[348,1297],{"x":1298,"y":1294,"width":1299,"height":394,"rx":351,"fill":361,"stroke":362,"style":402},"285.6","83.7",[369,1301,1305],{"x":1302,"y":1303,"style":1304},"327.5","35.0","font-size:11px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","col slot 0",[348,1307],{"x":1308,"y":1294,"width":1309,"height":394,"rx":351,"fill":361,"stroke":362,"style":402},"369.3","95.6",[369,1311,1313],{"x":1312,"y":1303,"style":1304},"417.1","col slot 1",[348,1315],{"x":1316,"y":1294,"width":1317,"height":394,"rx":351,"fill":361,"stroke":362,"style":402},"464.9","89.3",[369,1319,1321],{"x":1320,"y":1303,"style":1304},"509.5","col slot 2",[348,1323],{"x":1293,"y":1324,"width":1295,"height":394,"rx":351,"fill":361,"stroke":362,"style":402},"44.0",[369,1326,1329],{"x":1327,"y":1328,"style":1304},"245.7","63.0","row slot 0",[348,1331],{"x":1298,"y":1324,"width":1299,"height":394,"rx":351,"fill":565,"stroke":566,"style":402},[369,1333,1335],{"x":1302,"y":1328,"style":1334},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","North · A3",[348,1337],{"x":1308,"y":1324,"width":1309,"height":394,"rx":351,"fill":565,"stroke":566,"style":402},[369,1339,1340],{"x":1312,"y":1328,"style":1334},"South · G3",[348,1342],{"x":1316,"y":1324,"width":1317,"height":394,"rx":351,"fill":565,"stroke":566,"style":402},[369,1344,1345],{"x":1320,"y":1328,"style":1334},"East · M3",[348,1347],{"x":1293,"y":378,"width":1295,"height":394,"rx":351,"fill":361,"stroke":362,"style":402},[369,1349,1351],{"x":1327,"y":1350,"style":1304},"91.0","row slot 1",[348,1353],{"x":1298,"y":378,"width":1299,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,1355,1357],{"x":1302,"y":1350,"style":1356},"font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","West · A15",[348,1359],{"x":1308,"y":378,"width":1309,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,1361,1362],{"x":1312,"y":1350,"style":1356},"Central · G15",[348,1364],{"x":1316,"y":378,"width":1317,"height":394,"rx":351,"fill":400,"stroke":401,"style":402},[369,1366,1367],{"x":1320,"y":1350,"style":1356},"Coast · M15",[25,1369,1371],{"id":1370},"step-4-verify-what-was-written","Step 4 — Verify what was written",[10,1373,1374],{},"Reload the file and confirm each chart points at its own column and shares the scale:",[30,1376,1378],{"className":77,"code":1377,"language":79,"meta":35,"style":35},"from openpyxl import load_workbook\n\ncheck = load_workbook(\"regional_small_multiples.xlsx\")\nfor chart in check[\"Charts\"]._charts:\n    series = chart.series[0]\n    print(series.tx.strRef.f, series.val.numRef.f, \"max\", chart.y_axis.scaling.max)\n",[37,1379,1380,1391,1395,1409,1426,1441],{"__ignoreMap":35},[40,1381,1382,1384,1386,1388],{"class":42,"line":43},[40,1383,103],{"class":86},[40,1385,106],{"class":90},[40,1387,87],{"class":86},[40,1389,1390],{"class":90}," load_workbook\n",[40,1392,1393],{"class":42,"line":100},[40,1394,118],{"emptyLinePlaceholder":117},[40,1396,1397,1400,1402,1405,1407],{"class":42,"line":114},[40,1398,1399],{"class":90},"check ",[40,1401,127],{"class":86},[40,1403,1404],{"class":90}," load_workbook(",[40,1406,1264],{"class":50},[40,1408,136],{"class":90},[40,1410,1411,1413,1416,1418,1421,1423],{"class":42,"line":121},[40,1412,269],{"class":86},[40,1414,1415],{"class":90}," chart ",[40,1417,275],{"class":86},[40,1419,1420],{"class":90}," check[",[40,1422,1062],{"class":50},[40,1424,1425],{"class":90},"]._charts:\n",[40,1427,1428,1431,1433,1436,1438],{"class":42,"line":139},[40,1429,1430],{"class":90},"    series ",[40,1432,127],{"class":86},[40,1434,1435],{"class":90}," chart.series[",[40,1437,351],{"class":190},[40,1439,1440],{"class":90},"]\n",[40,1442,1443,1445,1448,1451],{"class":42,"line":182},[40,1444,1216],{"class":190},[40,1446,1447],{"class":90},"(series.tx.strRef.f, series.val.numRef.f, ",[40,1449,1450],{"class":50},"\"max\"",[40,1452,1453],{"class":90},", chart.y_axis.scaling.max)\n",[10,1455,1456,1457,159,1460,159,1463,1466,1467,23],{},"Every chart reads a different column — ",[37,1458,1459],{},"$B",[37,1461,1462],{},"$C",[37,1464,1465],{},"$D","… — with the same nine rows, and every axis maximum is the same. A test asserting these two properties protects the dashboard against an off-by-one in the loop, which would otherwise show one region's numbers under another region's title. For a visual check, render the chart sheet to PDF with LibreOffice as in ",[19,1468,1470],{"href":1469},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice\u002F","Convert Excel to PDF on Linux with LibreOffice",[25,1472,1474],{"id":1473},"step-5-variant-one-sheet-per-group","Step 5 — Variant: one sheet per group",[10,1476,1477],{},"When each group's owner should get \"their\" sheet — a tab per region with its table and its chart — write the group's rows to a new sheet and put the chart beside them:",[30,1479,1481],{"className":77,"code":1480,"language":79,"meta":35,"style":35},"wb2 = Workbook()\nwb2.remove(wb2.active)\nfor region, group in sales.groupby(\"Region\", sort=False):\n    sh = wb2.create_sheet(region)\n    sh.append([\"Month\", \"Sales\"])\n    for row in group[[\"Month\", \"Sales\"]].itertuples(index=False):\n        sh.append(list(row))\n    chart = LineChart()\n    chart.title = f\"{region} sales\"\n    chart.add_data(Reference(sh, min_col=2, min_row=1, max_row=len(group) + 1), titles_from_data=True)\n    chart.set_categories(Reference(sh, min_col=1, min_row=2, max_row=len(group) + 1))\n    chart.y_axis.scaling.min, chart.y_axis.scaling.max = 0, round(y_max, -2)\n    chart.x_axis.delete = chart.y_axis.delete = False\n    chart.legend = None\n    sh.add_chart(chart, \"D2\")\nwb2.save(\"regional_sheets.xlsx\")\nprint(load_workbook(\"regional_sheets.xlsx\").sheetnames)\n",[37,1482,1483,1492,1497,1524,1534,1548,1577,1588,1596,1616,1660,1695,1717,1730,1738,1748,1758],{"__ignoreMap":35},[40,1484,1485,1488,1490],{"class":42,"line":43},[40,1486,1487],{"class":90},"wb2 ",[40,1489,127],{"class":86},[40,1491,225],{"class":90},[40,1493,1494],{"class":42,"line":100},[40,1495,1496],{"class":90},"wb2.remove(wb2.active)\n",[40,1498,1499,1501,1504,1506,1509,1511,1513,1516,1518,1521],{"class":42,"line":114},[40,1500,269],{"class":86},[40,1502,1503],{"class":90}," region, group ",[40,1505,275],{"class":86},[40,1507,1508],{"class":90}," sales.groupby(",[40,1510,167],{"class":50},[40,1512,159],{"class":90},[40,1514,1515],{"class":150},"sort",[40,1517,127],{"class":86},[40,1519,1520],{"class":190},"False",[40,1522,1523],{"class":90},"):\n",[40,1525,1526,1529,1531],{"class":42,"line":121},[40,1527,1528],{"class":90},"    sh ",[40,1530,127],{"class":86},[40,1532,1533],{"class":90}," wb2.create_sheet(region)\n",[40,1535,1536,1539,1541,1543,1545],{"class":42,"line":139},[40,1537,1538],{"class":90},"    sh.append([",[40,1540,156],{"class":50},[40,1542,159],{"class":90},[40,1544,177],{"class":50},[40,1546,1547],{"class":90},"])\n",[40,1549,1550,1552,1555,1557,1560,1562,1564,1566,1569,1571,1573,1575],{"class":42,"line":182},[40,1551,914],{"class":86},[40,1553,1554],{"class":90}," row ",[40,1556,275],{"class":86},[40,1558,1559],{"class":90}," group[[",[40,1561,156],{"class":50},[40,1563,159],{"class":90},[40,1565,177],{"class":50},[40,1567,1568],{"class":90},"]].itertuples(",[40,1570,151],{"class":150},[40,1572,127],{"class":86},[40,1574,1520],{"class":190},[40,1576,1523],{"class":90},[40,1578,1579,1582,1585],{"class":42,"line":202},[40,1580,1581],{"class":90},"        sh.append(",[40,1583,1584],{"class":190},"list",[40,1586,1587],{"class":90},"(row))\n",[40,1589,1590,1592,1594],{"class":42,"line":212},[40,1591,736],{"class":90},[40,1593,127],{"class":86},[40,1595,741],{"class":90},[40,1597,1598,1600,1602,1604,1606,1608,1611,1613],{"class":42,"line":217},[40,1599,746],{"class":90},[40,1601,127],{"class":86},[40,1603,1159],{"class":86},[40,1605,1162],{"class":50},[40,1607,1166],{"class":1165},[40,1609,1610],{"class":90},"region",[40,1612,1203],{"class":1165},[40,1614,1615],{"class":50}," sales\"\n",[40,1617,1618,1621,1623,1625,1627,1629,1631,1633,1635,1637,1639,1641,1643,1646,1648,1650,1652,1654,1656,1658],{"class":42,"line":228},[40,1619,1620],{"class":90},"    chart.add_data(Reference(sh, ",[40,1622,759],{"class":150},[40,1624,127],{"class":86},[40,1626,423],{"class":190},[40,1628,159],{"class":90},[40,1630,767],{"class":150},[40,1632,127],{"class":86},[40,1634,317],{"class":190},[40,1636,159],{"class":90},[40,1638,776],{"class":150},[40,1640,127],{"class":86},[40,1642,991],{"class":190},[40,1644,1645],{"class":90},"(group) ",[40,1647,783],{"class":86},[40,1649,786],{"class":190},[40,1651,789],{"class":90},[40,1653,792],{"class":150},[40,1655,127],{"class":86},[40,1657,797],{"class":190},[40,1659,136],{"class":90},[40,1661,1662,1665,1667,1669,1671,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691,1693],{"class":42,"line":239},[40,1663,1664],{"class":90},"    chart.set_categories(Reference(sh, ",[40,1666,759],{"class":150},[40,1668,127],{"class":86},[40,1670,317],{"class":190},[40,1672,159],{"class":90},[40,1674,767],{"class":150},[40,1676,127],{"class":86},[40,1678,423],{"class":190},[40,1680,159],{"class":90},[40,1682,776],{"class":150},[40,1684,127],{"class":86},[40,1686,991],{"class":190},[40,1688,1645],{"class":90},[40,1690,783],{"class":86},[40,1692,786],{"class":190},[40,1694,833],{"class":90},[40,1696,1697,1700,1702,1705,1707,1709,1711,1713,1715],{"class":42,"line":250},[40,1698,1699],{"class":90},"    chart.y_axis.scaling.min, chart.y_axis.scaling.max ",[40,1701,127],{"class":86},[40,1703,1704],{"class":190}," 0",[40,1706,159],{"class":90},[40,1708,1002],{"class":190},[40,1710,866],{"class":90},[40,1712,869],{"class":86},[40,1714,423],{"class":190},[40,1716,136],{"class":90},[40,1718,1719,1721,1723,1726,1728],{"class":42,"line":266},[40,1720,878],{"class":90},[40,1722,127],{"class":86},[40,1724,1725],{"class":90}," chart.y_axis.delete ",[40,1727,127],{"class":86},[40,1729,883],{"class":190},[40,1731,1732,1734,1736],{"class":42,"line":281},[40,1733,838],{"class":90},[40,1735,127],{"class":86},[40,1737,843],{"class":190},[40,1739,1740,1743,1746],{"class":42,"line":292},[40,1741,1742],{"class":90},"    sh.add_chart(chart, ",[40,1744,1745],{"class":50},"\"D2\"",[40,1747,136],{"class":90},[40,1749,1750,1753,1756],{"class":42,"line":895},[40,1751,1752],{"class":90},"wb2.save(",[40,1754,1755],{"class":50},"\"regional_sheets.xlsx\"",[40,1757,136],{"class":90},[40,1759,1760,1762,1765,1767],{"class":42,"line":911},[40,1761,295],{"class":190},[40,1763,1764],{"class":90},"(load_workbook(",[40,1766,1755],{"class":50},[40,1768,1769],{"class":90},").sheetnames)\n",[10,1771,1772,1773,23],{},"Each sheet holds one region's rows and a chart that references them — with the same shared maximum, so a manager flipping between tabs sees comparable charts. Splitting the sheets into separate files for distribution is covered in ",[19,1774,1776],{"href":1775},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsave-each-sheet-of-a-workbook-as-a-separate-file\u002F","Save Each Sheet of a Workbook as a Separate File",[329,1778,338,1784,338,1787,338,1790,338,1794,338,1800,338,1811,338,1817,338,1821,338,1826,338,1833,338,1837,338,1842,338,1846,338,1853,338,1856,338,1863,338,1868,338,1872,338,1876,338,1881,338,1884,338,1889,338,1892],{"viewBox":1779,"role":332,"ariaLabelledBy":1780,"xmlns":336,"style":1783},"4 0 752 207",[1781,1782],"cg-variants-t","cg-variants-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[340,1785,1786],{"id":1781},"A grid on one sheet or a sheet per group",[344,1788,1789],{"id":1782},"A grid of small charts on one sheet is best for comparing groups at a glance. One sheet per group with its own table and chart is best when each group's owner should see only their data, and it splits easily into separate files.",[348,1791],{"x":467,"y":351,"width":1792,"height":1793,"fill":354},"752","207",[348,1795],{"x":1796,"y":1294,"width":1797,"height":1798,"rx":1799,"fill":400,"stroke":401,"style":636},"20.0","351.0","175.0","14",[1801,1802,1804],"g",{"transform":1803},"translate(182.5,29.0) scale(1.083)",[1805,1806],"path",{"d":1807,"fill":1808,"stroke":1809,"style":1810},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","none","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[369,1812,1816],{"x":1813,"y":1814,"style":1815},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Grid on one sheet",[42,1818],{"x1":1819,"y1":1350,"x2":1820,"y2":1350,"stroke":401,"style":363},"36.0","355.0",[369,1822,1825],{"x":1823,"y":422,"style":1824},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","compare all groups at a glance",[1801,1827,1829],{"transform":1828},"translate(38.5,99.5) scale(0.625)",[1805,1830],{"d":1831,"fill":1808,"stroke":566,"style":1832},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[369,1834,1836],{"x":1823,"y":1835,"style":1824},"137.0","one page to print",[1801,1838,1840],{"transform":1839},"translate(38.5,125.5) scale(0.625)",[1805,1841],{"d":1831,"fill":1808,"stroke":566,"style":1832},[369,1843,1845],{"x":1823,"y":1844,"style":1824},"163.0","data lives on a separate sheet",[1801,1847,1849],{"transform":1848},"translate(38.5,151.5) scale(0.625)",[1805,1850],{"d":1851,"fill":1808,"stroke":1852,"style":1832},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)",[348,1854],{"x":1855,"y":1294,"width":1797,"height":1798,"rx":1799,"fill":565,"stroke":566,"style":636},"389.0",[1801,1857,1859],{"transform":1858},"translate(551.5,29.0) scale(1.083)",[1805,1860],{"d":1861,"fill":1808,"stroke":1862,"style":1810},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--teal-ink,#0b6157)",[369,1864,1867],{"x":1865,"y":1814,"style":1866},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","One sheet per group",[42,1869],{"x1":1870,"y1":1350,"x2":1871,"y2":1350,"stroke":566,"style":363},"405.0","724.0",[369,1873,1875],{"x":1874,"y":422,"style":1824},"429.0","owner sees table and chart together",[1801,1877,1879],{"transform":1878},"translate(407.5,99.5) scale(0.625)",[1805,1880],{"d":1831,"fill":1808,"stroke":566,"style":1832},[369,1882,1883],{"x":1874,"y":1835,"style":1824},"easy to split into files",[1801,1885,1887],{"transform":1886},"translate(407.5,125.5) scale(0.625)",[1805,1888],{"d":1831,"fill":1808,"stroke":566,"style":1832},[369,1890,1891],{"x":1874,"y":1844,"style":1824},"comparing groups means flipping tabs",[1801,1893,1895],{"transform":1894},"translate(407.5,151.5) scale(0.625)",[1805,1896],{"d":1851,"fill":1808,"stroke":1852,"style":1832},[25,1898,1900],{"id":1899},"performance-notes","Performance notes",[10,1902,1903,1904,23],{},"Each chart is a small XML part, so dozens of charts add little to the file size or to the time it takes to write it — six charts here write in a fraction of a second. The practical limits are human and visual: Excel redraws every chart on a sheet when it opens or recalculates, so a sheet with a hundred charts feels sluggish, and nobody reads a hundred charts anyway. Keep the grid to the groups that matter — the top twelve by revenue, say, plus an \"all other\" chart — and move the rest to a detail sheet or a separate file. If the data has thousands of groups, a heatmap table often communicates better than any number of charts; see ",[19,1905,1907],{"href":1906},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fturn-a-pivot-table-into-a-heatmap-in-excel\u002F","Turn a Pivot Table into a Heatmap in Excel",[25,1909,1911],{"id":1910},"common-pitfalls","Common pitfalls",[1913,1914,1915,1931,1937,1943,1951],"ul",{},[1916,1917,1918,1922,1923,1926,1927,1930],"li",{},[1919,1920,1921],"strong",{},"Independent axis scales."," Each chart auto-scales to its own data; set the same ",[37,1924,1925],{},"min"," and ",[37,1928,1929],{},"max"," everywhere.",[1916,1932,1933,1936],{},[1919,1934,1935],{},"Overlapping charts."," Compute anchors from a grid larger than the chart size.",[1916,1938,1939,1942],{},[1919,1940,1941],{},"Off-by-one references."," Header row 1, data from row 2; check each chart's ranges after writing.",[1916,1944,1945,1948,1949,23],{},[1919,1946,1947],{},"Smoothed lines."," openpyxl line series are smoothed unless ",[37,1950,1016],{},[1916,1952,1953,1956],{},[1919,1954,1955],{},"Too many charts on one sheet."," Beyond a dozen or so, split by sheet or filter to the groups that matter.",[25,1958,1960],{"id":1959},"conclusion","Conclusion",[10,1962,1963,1964,1967],{},"Pivot the long data so each group is a contiguous column, create one chart per column in a loop with its own value range and the shared category range, and give every chart the same axis limits so they can be compared. Place the charts in a grid with anchors computed from ",[37,1965,1966],{},"divmod",", make the chart sheet active, verify each chart's ranges in a test, and use one sheet per group when the charts are meant for different owners.",[25,1969,1971],{"id":1970},"frequently-asked-questions","Frequently asked questions",[10,1973,1974,1977],{},[1919,1975,1976],{},"How do I create a chart for each group in a DataFrame with openpyxl?","\nPivot the data so each group has its own column, then loop over the columns and create a chart per column with Reference objects for that column's values and the shared category column. Add each chart at its own anchor cell.",[10,1979,1980,1983],{},[1919,1981,1982],{},"How do I place many charts in a grid without overlapping?","\nCompute each chart's anchor from its position: row and column slot from divmod(index, charts_per_row), multiplied by a slot size in columns and rows that is larger than the chart.",[10,1985,1986,1989],{},[1919,1987,1988],{},"How do I give all small charts the same y-axis scale?","\nSet chart.y_axis.scaling.min and chart.y_axis.scaling.max to the same values on every chart, typically zero and the overall maximum plus a margin.",[10,1991,1992,1995],{},[1919,1993,1994],{},"How do I put each group on its own sheet with its chart?","\nGroup the DataFrame, create a sheet per group, write the group's rows, and add a chart that references that sheet's range. Use the same axis limits so the sheets are comparable.",[25,1997,1999],{"id":1998},"related","Related",[1913,2001,2002,2008,2015,2022],{},[1916,2003,2004,2005,2007],{},"Up one level: ",[19,2006,22],{"href":21}," — native charts from Python.",[1916,2009,2010,2014],{},[19,2011,2013],{"href":2012},"\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 referencing several sheets.",[1916,2016,2017,2021],{},[19,2018,2020],{"href":2019},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcreate-a-sheet-for-each-group-in-a-dataframe\u002F","Create a Sheet for Each Group in a DataFrame"," — the sheet-per-group pattern without charts.",[1916,2023,2024,2028],{},[19,2025,2027],{"href":2026},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fmake-an-excel-chart-grow-with-its-data\u002F","Make an Excel Chart Grow with Its Data"," — keeping chart ranges current.",[2030,2031,2032],"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 .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 .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}",{"title":35,"searchDepth":100,"depth":100,"links":2034},[2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045],{"id":27,"depth":100,"text":28},{"id":70,"depth":100,"text":71},{"id":658,"depth":100,"text":659},{"id":1024,"depth":100,"text":1025},{"id":1370,"depth":100,"text":1371},{"id":1473,"depth":100,"text":1474},{"id":1899,"depth":100,"text":1900},{"id":1910,"depth":100,"text":1911},{"id":1959,"depth":100,"text":1960},{"id":1970,"depth":100,"text":1971},{"id":1998,"depth":100,"text":1999},"2026-10-02","Turn a long table — sales by region and month — into a grid of small charts, one per region, generated in a loop: write each group as its own block, build a chart that references only that block, lay the charts out in a grid with computed anchors, give every chart the same axis scale so they can be compared, or put each group on its own sheet with its chart.","md",[2050,2052,2054,2056],{"q":1976,"a":2051},"Pivot the data so each group has its own column, then loop over the columns and create a chart per column with Reference objects for that column's values and the shared category column. Add each chart at its own anchor cell.",{"q":1982,"a":2053},"Compute each chart's anchor from its position: row and column slot from divmod(index, charts_per_row), multiplied by a slot size in columns and rows that is larger than the chart.",{"q":1988,"a":2055},"Set chart.y_axis.scaling.min and chart.y_axis.scaling.max to the same values on every chart, typically zero and the overall maximum plus a margin.",{"q":1994,"a":2057},"Group the DataFrame, create a sheet per group, write the group's rows, and add a chart that references that sheet's range. Use the same axis limits so the sheets are comparable.","create one chart per group with openpyxl",null,{"breadcrumb":2061},[2062,2065,2068],{"name":2063,"item":2064},"Home","\u002F",{"name":2066,"item":2067},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl",{"title":2071,"description":2072},"Create One Excel Chart per Group in a Loop with openpyxl","Small multiples in Excel from Python: group a DataFrame, write each group as a block, create a LineChart per block with Reference, place charts in a grid with calculated anchors, share one y-axis maximum, and the one-sheet-per-group variant.","create-one-chart-per-group-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl\u002Findex",[2076,2077],"charts-and-images","sheets-and-files","how-to","Qn8CRmUN5xvLt7xuNHkEjhB0SAyKM4tVTkMqkhvgeb0",[2081,2085],{"title":2082,"path":2083,"stem":2084,"children":-1},"Create a Bar Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl\u002Findex",{"title":2086,"path":2087,"stem":2088,"children":-1},"Format Chart Axes with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl\u002Findex",[2090,2092],{"path":2091,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":2093,"title":2066},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2096,2098,2101,2104,2107,2110],{"path":2097,"title":2013,"kind":2078},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs",{"path":2099,"title":2100,"kind":2078},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","Build a Rolling 12-Month Chart on an Excel Dashboard",{"path":2102,"title":2103,"kind":2078},"\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":2105,"title":2106,"kind":2078},"\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":2108,"title":2109,"kind":2078},"\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":2111,"title":2112,"kind":2078},"\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",1790972111294]