[{"data":1,"prerenderedAt":2491},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs":2483},{"id":4,"title":5,"body":6,"dateModified":2455,"datePublished":2455,"description":2456,"extension":2457,"faq":2458,"meta":2467,"navigation":182,"path":2476,"seo":2477,"slug":2479,"stem":2480,"type":2481,"__hash__":2482},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs\u002Findex.md","Build a Dashboard Sheet with Charts from Multiple Tabs",{"type":7,"value":8,"toc":2439},"minimark",[9,19,118,123,154,392,396,399,521,524,528,629,974,993,997,1193,1196,1200,1365,1368,1372,1379,1640,1649,1653,1656,1859,1862,1900,1908,1912,1915,2037,2047,2051,2150,2154,2157,2250,2262,2266,2351,2354,2357,2361,2364,2368,2375,2381,2387,2393,2397,2435],[10,11,12,13,18],"p",{},"The point of a dashboard sheet is that a reader never has to visit the tabs behind it. That means\ncharts whose data lives elsewhere in the workbook — one series from the monthly tab, another from the\nregional one — assembled onto a single page with a deliberate layout. This guide, part of\n",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002F","Building Multi-Sheet Excel Dashboards",",\nbuilds that page with xlsxwriter and shows the openpyxl equivalent.",[20,21,29,30,29,34,29,38,29,45,29,52,29,62,29,68,29,73,29,80,29,85,29,88,29,91,29,93,29,97,29,100,29,105,29,110,29,113],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"0 0 760 232","img",[25,26],"dsh-refs-t","dsh-refs-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"The dashboard sheet reads, it does not hold",[35,36,37],"desc",{"id":26},"Data frames are written to their own tabs, and the dashboard sheet carries only charts whose series point back at those ranges, so the reader never opens the data tabs.",[39,40],"rect",{"x":41,"y":41,"width":42,"height":43,"fill":44},"0","760","232","#ffffff",[46,47,51],"text",{"x":48,"y":49,"style":50},"380.0","32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","one page, several sources",[39,53],{"x":54,"y":55,"width":56,"height":57,"rx":58,"fill":59,"stroke":60,"style":61},"24.0","74","208.0","96","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[46,63,67],{"x":64,"y":65,"style":66},"128.0","114","font-size:14px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","Monthly tab",[46,69,72],{"x":64,"y":70,"style":71},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","written first",[74,75],"line",{"x1":76,"y1":77,"x2":78,"y2":77,"stroke":79,"style":61},"237.0","122.0","269.0","var(--brand,#5b5cf0)",[81,82],"polygon",{"points":83,"fill":84},"269.0,122.0 260.0,117.0 260.0,127.0","#5b5cf0",[39,86],{"x":87,"y":55,"width":56,"height":57,"rx":58,"fill":59,"stroke":60,"style":61},"276.0",[46,89,90],{"x":48,"y":65,"style":66},"Regional tab",[46,92,72],{"x":48,"y":70,"style":71},[74,94],{"x1":95,"y1":77,"x2":96,"y2":77,"stroke":79,"style":61},"489.0","521.0",[81,98],{"points":99,"fill":84},"521.0,122.0 512.0,117.0 512.0,127.0",[39,101],{"x":102,"y":55,"width":56,"height":57,"rx":58,"fill":103,"stroke":104,"style":61},"528.0","#d9f4f1","var(--teal,#0f9488)",[46,106,109],{"x":107,"y":65,"style":108},"632.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Dashboard",[46,111,112],{"x":107,"y":70,"style":71},"charts reference both",[46,114,117],{"x":48,"y":115,"style":116},"210","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the data lives once; the dashboard only points at it",[119,120,122],"h2",{"id":121},"prerequisites","Prerequisites",[124,125,130],"pre",{"className":126,"code":127,"language":128,"meta":129,"style":129},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas xlsxwriter openpyxl\n","bash","",[131,132,133],"code",{"__ignoreMap":129},[134,135,137,141,145,148,151],"span",{"class":74,"line":136},1,[134,138,140],{"class":139},"sMTad","pip",[134,142,144],{"class":143},"srMev"," install",[134,146,147],{"class":143}," pandas",[134,149,150],{"class":143}," xlsxwriter",[134,152,153],{"class":143}," openpyxl\n",[124,155,159],{"className":156,"code":157,"language":158,"meta":129,"style":129},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nmonthly = pd.DataFrame({\n    \"Month\": [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"],\n    \"Revenue\": [31200.0, 28400.0, 34100.0, 29800.0, 36500.0, 41200.0],\n    \"Target\": [30000.0, 30000.0, 32000.0, 32000.0, 34000.0, 34000.0],\n})\n\nregional = pd.DataFrame({\n    \"Region\": [\"North\", \"South\", \"West\", \"East\"],\n    \"Revenue\": [86400.0, 52300.0, 38900.0, 23600.0],\n})\n","python",[131,160,161,177,184,196,237,276,311,317,322,332,360,387],{"__ignoreMap":129},[134,162,163,167,171,174],{"class":74,"line":136},[134,164,166],{"class":165},"s-kum","import",[134,168,170],{"class":169},"skGVy"," pandas ",[134,172,173],{"class":165},"as",[134,175,176],{"class":169}," pd\n",[134,178,180],{"class":74,"line":179},2,[134,181,183],{"emptyLinePlaceholder":182},true,"\n",[134,185,187,190,193],{"class":74,"line":186},3,[134,188,189],{"class":169},"monthly ",[134,191,192],{"class":165},"=",[134,194,195],{"class":169}," pd.DataFrame({\n",[134,197,199,202,205,208,211,214,216,219,221,224,226,229,231,234],{"class":74,"line":198},4,[134,200,201],{"class":143},"    \"Month\"",[134,203,204],{"class":169},": [",[134,206,207],{"class":143},"\"Jan\"",[134,209,210],{"class":169},", ",[134,212,213],{"class":143},"\"Feb\"",[134,215,210],{"class":169},[134,217,218],{"class":143},"\"Mar\"",[134,220,210],{"class":169},[134,222,223],{"class":143},"\"Apr\"",[134,225,210],{"class":169},[134,227,228],{"class":143},"\"May\"",[134,230,210],{"class":169},[134,232,233],{"class":143},"\"Jun\"",[134,235,236],{"class":169},"],\n",[134,238,240,243,245,249,251,254,256,259,261,264,266,269,271,274],{"class":74,"line":239},5,[134,241,242],{"class":143},"    \"Revenue\"",[134,244,204],{"class":169},[134,246,248],{"class":247},"sP0c6","31200.0",[134,250,210],{"class":169},[134,252,253],{"class":247},"28400.0",[134,255,210],{"class":169},[134,257,258],{"class":247},"34100.0",[134,260,210],{"class":169},[134,262,263],{"class":247},"29800.0",[134,265,210],{"class":169},[134,267,268],{"class":247},"36500.0",[134,270,210],{"class":169},[134,272,273],{"class":247},"41200.0",[134,275,236],{"class":169},[134,277,279,282,284,287,289,291,293,296,298,300,302,305,307,309],{"class":74,"line":278},6,[134,280,281],{"class":143},"    \"Target\"",[134,283,204],{"class":169},[134,285,286],{"class":247},"30000.0",[134,288,210],{"class":169},[134,290,286],{"class":247},[134,292,210],{"class":169},[134,294,295],{"class":247},"32000.0",[134,297,210],{"class":169},[134,299,295],{"class":247},[134,301,210],{"class":169},[134,303,304],{"class":247},"34000.0",[134,306,210],{"class":169},[134,308,304],{"class":247},[134,310,236],{"class":169},[134,312,314],{"class":74,"line":313},7,[134,315,316],{"class":169},"})\n",[134,318,320],{"class":74,"line":319},8,[134,321,183],{"emptyLinePlaceholder":182},[134,323,325,328,330],{"class":74,"line":324},9,[134,326,327],{"class":169},"regional ",[134,329,192],{"class":165},[134,331,195],{"class":169},[134,333,335,338,340,343,345,348,350,353,355,358],{"class":74,"line":334},10,[134,336,337],{"class":143},"    \"Region\"",[134,339,204],{"class":169},[134,341,342],{"class":143},"\"North\"",[134,344,210],{"class":169},[134,346,347],{"class":143},"\"South\"",[134,349,210],{"class":169},[134,351,352],{"class":143},"\"West\"",[134,354,210],{"class":169},[134,356,357],{"class":143},"\"East\"",[134,359,236],{"class":169},[134,361,363,365,367,370,372,375,377,380,382,385],{"class":74,"line":362},11,[134,364,242],{"class":143},[134,366,204],{"class":169},[134,368,369],{"class":247},"86400.0",[134,371,210],{"class":169},[134,373,374],{"class":247},"52300.0",[134,376,210],{"class":169},[134,378,379],{"class":247},"38900.0",[134,381,210],{"class":169},[134,383,384],{"class":247},"23600.0",[134,386,236],{"class":169},[134,388,390],{"class":74,"line":389},12,[134,391,316],{"class":169},[119,393,395],{"id":394},"write-the-data-tabs-first","Write the data tabs first",[10,397,398],{},"Charts reference ranges, so the data has to exist before the chart that points at it. Writing each\nframe to its own sheet and recording where it landed keeps the references derived rather than\nguessed.",[124,400,402],{"className":156,"code":401,"language":158,"meta":129,"style":129},"with pd.ExcelWriter(\"dashboard.xlsx\", engine=\"xlsxwriter\") as writer:\n    monthly.to_excel(writer, sheet_name=\"Monthly\", index=False)\n    regional.to_excel(writer, sheet_name=\"Regional\", index=False)\n    book = writer.book\n    dashboard = book.add_worksheet(\"Dashboard\")\n    book.worksheets_objs.insert(0, book.worksheets_objs.pop())     # put it first\n",[131,403,404,434,460,482,492,507],{"__ignoreMap":129},[134,405,406,409,412,415,417,421,423,426,429,431],{"class":74,"line":136},[134,407,408],{"class":165},"with",[134,410,411],{"class":169}," pd.ExcelWriter(",[134,413,414],{"class":143},"\"dashboard.xlsx\"",[134,416,210],{"class":169},[134,418,420],{"class":419},"sa561","engine",[134,422,192],{"class":165},[134,424,425],{"class":143},"\"xlsxwriter\"",[134,427,428],{"class":169},") ",[134,430,173],{"class":165},[134,432,433],{"class":169}," writer:\n",[134,435,436,439,442,444,447,449,452,454,457],{"class":74,"line":179},[134,437,438],{"class":169},"    monthly.to_excel(writer, ",[134,440,441],{"class":419},"sheet_name",[134,443,192],{"class":165},[134,445,446],{"class":143},"\"Monthly\"",[134,448,210],{"class":169},[134,450,451],{"class":419},"index",[134,453,192],{"class":165},[134,455,456],{"class":247},"False",[134,458,459],{"class":169},")\n",[134,461,462,465,467,469,472,474,476,478,480],{"class":74,"line":186},[134,463,464],{"class":169},"    regional.to_excel(writer, ",[134,466,441],{"class":419},[134,468,192],{"class":165},[134,470,471],{"class":143},"\"Regional\"",[134,473,210],{"class":169},[134,475,451],{"class":419},[134,477,192],{"class":165},[134,479,456],{"class":247},[134,481,459],{"class":169},[134,483,484,487,489],{"class":74,"line":198},[134,485,486],{"class":169},"    book ",[134,488,192],{"class":165},[134,490,491],{"class":169}," writer.book\n",[134,493,494,497,499,502,505],{"class":74,"line":239},[134,495,496],{"class":169},"    dashboard ",[134,498,192],{"class":165},[134,500,501],{"class":169}," book.add_worksheet(",[134,503,504],{"class":143},"\"Dashboard\"",[134,506,459],{"class":169},[134,508,509,512,514,517],{"class":74,"line":278},[134,510,511],{"class":169},"    book.worksheets_objs.insert(",[134,513,41],{"class":247},[134,515,516],{"class":169},", book.worksheets_objs.pop())     ",[134,518,520],{"class":519},"s-wDw","# put it first\n",[10,522,523],{},"Moving the dashboard to the front matters more than it sounds: a workbook opens on whichever sheet\nwas active when it was saved, and a reader who lands on a raw data tab forms an impression the\ndashboard was supposed to prevent.",[119,525,527],{"id":526},"a-chart-whose-data-lives-on-another-sheet","A chart whose data lives on another sheet",[20,529,29,534,29,537,29,540,29,543,29,550,29,555,29,560,29,566,29,571,29,574,29,577,29,581,29,585,29,589,29,592,29,595,29,599,29,603,29,607,29,610,29,613,29,617,29,621,29,625],{"viewBox":530,"role":23,"ariaLabelledBy":531,"xmlns":27,"style":28},"0 0 760 308",[532,533],"dsh-series-t","dsh-series-d",[31,535,536],{"id":532},"Building a cross-sheet series reference",[35,538,539],{"id":533},"Name the sheet the data is on, start the range below the header row, take the last row from the frame's length, and anchor the finished chart to a cell in the layout grid.",[39,541],{"x":41,"y":41,"width":42,"height":542,"fill":44},"308",[39,544],{"x":545,"y":546,"width":547,"height":548,"rx":549,"fill":103,"stroke":104},"22","28","716","52","10",[551,552],"circle",{"cx":548,"cy":553,"r":554,"fill":84},"54.0","15",[46,556,559],{"x":548,"y":557,"style":558},"59.0","font-size:13px;font-weight:700;fill:#ffffff;text-anchor:middle","1",[46,561,565],{"x":562,"y":563,"style":564},"82","50","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","Name the source sheet",[46,567,570],{"x":562,"y":568,"style":569},"68","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:start","case-sensitive; quote names with spaces",[39,572],{"x":545,"y":573,"width":547,"height":548,"rx":549,"fill":103,"stroke":104},"90",[551,575],{"cx":548,"cy":576,"r":554,"fill":84},"116.0",[46,578,580],{"x":548,"y":579,"style":558},"121.0","2",[46,582,584],{"x":562,"y":583,"style":564},"112","Start below the header",[46,586,588],{"x":562,"y":587,"style":569},"130","row 1, not row 0, for the values",[39,590],{"x":545,"y":591,"width":547,"height":548,"rx":549,"fill":103,"stroke":104},"152",[551,593],{"cx":548,"cy":594,"r":554,"fill":84},"178.0",[46,596,598],{"x":548,"y":597,"style":558},"183.0","3",[46,600,602],{"x":562,"y":601,"style":564},"174","Size from len(frame)",[46,604,606],{"x":562,"y":605,"style":569},"192","so new rows are included next run",[39,608],{"x":545,"y":609,"width":547,"height":548,"rx":549,"fill":103,"stroke":104},"214",[551,611],{"cx":548,"cy":612,"r":554,"fill":84},"240.0",[46,614,616],{"x":548,"y":615,"style":558},"245.0","4",[46,618,620],{"x":562,"y":619,"style":564},"236","Anchor to a cell",[46,622,624],{"x":562,"y":623,"style":569},"254","offsets move when a column resizes",[46,626,628],{"x":48,"y":627,"style":116},"296","a hard-coded range is a chart that silently stops updating",[124,630,632],{"className":156,"code":631,"language":158,"meta":129,"style":129},"rows = len(monthly)\n\nline = book.add_chart({\"type\": \"line\"})\nline.add_series({\n    \"name\":       \"Revenue\",\n    \"categories\": [\"Monthly\", 1, 0, rows, 0],\n    \"values\":     [\"Monthly\", 1, 1, rows, 1],\n    \"line\":       {\"color\": \"#5B5CF0\", \"width\": 2.25},\n})\nline.add_series({\n    \"name\":       \"Target\",\n    \"categories\": [\"Monthly\", 1, 0, rows, 0],\n    \"values\":     [\"Monthly\", 1, 2, rows, 2],\n    \"line\":       {\"color\": \"#B4740A\", \"width\": 1.5, \"dash_type\": \"dash\"},\n})\nline.set_title({\"name\": \"Revenue against target\"})\nline.set_y_axis({\"num_format\": \"#,##0\", \"major_gridlines\": {\"visible\": True}})\nline.set_legend({\"position\": \"bottom\"})\nline.set_size({\"width\": 480, \"height\": 280})\ndashboard.insert_chart(\"B8\", line)\n",[131,633,634,647,651,672,677,691,715,739,768,772,776,787,809,832,867,872,888,921,937,962],{"__ignoreMap":129},[134,635,636,639,641,644],{"class":74,"line":136},[134,637,638],{"class":169},"rows ",[134,640,192],{"class":165},[134,642,643],{"class":247}," len",[134,645,646],{"class":169},"(monthly)\n",[134,648,649],{"class":74,"line":179},[134,650,183],{"emptyLinePlaceholder":182},[134,652,653,656,658,661,664,667,670],{"class":74,"line":186},[134,654,655],{"class":169},"line ",[134,657,192],{"class":165},[134,659,660],{"class":169}," book.add_chart({",[134,662,663],{"class":143},"\"type\"",[134,665,666],{"class":169},": ",[134,668,669],{"class":143},"\"line\"",[134,671,316],{"class":169},[134,673,674],{"class":74,"line":198},[134,675,676],{"class":169},"line.add_series({\n",[134,678,679,682,685,688],{"class":74,"line":239},[134,680,681],{"class":143},"    \"name\"",[134,683,684],{"class":169},":       ",[134,686,687],{"class":143},"\"Revenue\"",[134,689,690],{"class":169},",\n",[134,692,693,696,698,700,702,704,706,708,711,713],{"class":74,"line":278},[134,694,695],{"class":143},"    \"categories\"",[134,697,204],{"class":169},[134,699,446],{"class":143},[134,701,210],{"class":169},[134,703,559],{"class":247},[134,705,210],{"class":169},[134,707,41],{"class":247},[134,709,710],{"class":169},", rows, ",[134,712,41],{"class":247},[134,714,236],{"class":169},[134,716,717,720,723,725,727,729,731,733,735,737],{"class":74,"line":313},[134,718,719],{"class":143},"    \"values\"",[134,721,722],{"class":169},":     [",[134,724,446],{"class":143},[134,726,210],{"class":169},[134,728,559],{"class":247},[134,730,210],{"class":169},[134,732,559],{"class":247},[134,734,710],{"class":169},[134,736,559],{"class":247},[134,738,236],{"class":169},[134,740,741,744,747,750,752,755,757,760,762,765],{"class":74,"line":319},[134,742,743],{"class":143},"    \"line\"",[134,745,746],{"class":169},":       {",[134,748,749],{"class":143},"\"color\"",[134,751,666],{"class":169},[134,753,754],{"class":143},"\"#5B5CF0\"",[134,756,210],{"class":169},[134,758,759],{"class":143},"\"width\"",[134,761,666],{"class":169},[134,763,764],{"class":247},"2.25",[134,766,767],{"class":169},"},\n",[134,769,770],{"class":74,"line":324},[134,771,316],{"class":169},[134,773,774],{"class":74,"line":334},[134,775,676],{"class":169},[134,777,778,780,782,785],{"class":74,"line":362},[134,779,681],{"class":143},[134,781,684],{"class":169},[134,783,784],{"class":143},"\"Target\"",[134,786,690],{"class":169},[134,788,789,791,793,795,797,799,801,803,805,807],{"class":74,"line":389},[134,790,695],{"class":143},[134,792,204],{"class":169},[134,794,446],{"class":143},[134,796,210],{"class":169},[134,798,559],{"class":247},[134,800,210],{"class":169},[134,802,41],{"class":247},[134,804,710],{"class":169},[134,806,41],{"class":247},[134,808,236],{"class":169},[134,810,812,814,816,818,820,822,824,826,828,830],{"class":74,"line":811},13,[134,813,719],{"class":143},[134,815,722],{"class":169},[134,817,446],{"class":143},[134,819,210],{"class":169},[134,821,559],{"class":247},[134,823,210],{"class":169},[134,825,580],{"class":247},[134,827,710],{"class":169},[134,829,580],{"class":247},[134,831,236],{"class":169},[134,833,835,837,839,841,843,846,848,850,852,855,857,860,862,865],{"class":74,"line":834},14,[134,836,743],{"class":143},[134,838,746],{"class":169},[134,840,749],{"class":143},[134,842,666],{"class":169},[134,844,845],{"class":143},"\"#B4740A\"",[134,847,210],{"class":169},[134,849,759],{"class":143},[134,851,666],{"class":169},[134,853,854],{"class":247},"1.5",[134,856,210],{"class":169},[134,858,859],{"class":143},"\"dash_type\"",[134,861,666],{"class":169},[134,863,864],{"class":143},"\"dash\"",[134,866,767],{"class":169},[134,868,870],{"class":74,"line":869},15,[134,871,316],{"class":169},[134,873,875,878,881,883,886],{"class":74,"line":874},16,[134,876,877],{"class":169},"line.set_title({",[134,879,880],{"class":143},"\"name\"",[134,882,666],{"class":169},[134,884,885],{"class":143},"\"Revenue against target\"",[134,887,316],{"class":169},[134,889,891,894,897,899,902,904,907,910,913,915,918],{"class":74,"line":890},17,[134,892,893],{"class":169},"line.set_y_axis({",[134,895,896],{"class":143},"\"num_format\"",[134,898,666],{"class":169},[134,900,901],{"class":143},"\"#,##0\"",[134,903,210],{"class":169},[134,905,906],{"class":143},"\"major_gridlines\"",[134,908,909],{"class":169},": {",[134,911,912],{"class":143},"\"visible\"",[134,914,666],{"class":169},[134,916,917],{"class":247},"True",[134,919,920],{"class":169},"}})\n",[134,922,924,927,930,932,935],{"class":74,"line":923},18,[134,925,926],{"class":169},"line.set_legend({",[134,928,929],{"class":143},"\"position\"",[134,931,666],{"class":169},[134,933,934],{"class":143},"\"bottom\"",[134,936,316],{"class":169},[134,938,940,943,945,947,950,952,955,957,960],{"class":74,"line":939},19,[134,941,942],{"class":169},"line.set_size({",[134,944,759],{"class":143},[134,946,666],{"class":169},[134,948,949],{"class":247},"480",[134,951,210],{"class":169},[134,953,954],{"class":143},"\"height\"",[134,956,666],{"class":169},[134,958,959],{"class":247},"280",[134,961,316],{"class":169},[134,963,965,968,971],{"class":74,"line":964},20,[134,966,967],{"class":169},"dashboard.insert_chart(",[134,969,970],{"class":143},"\"B8\"",[134,972,973],{"class":169},", line)\n",[10,975,976,977,980,981,984,985,988,989,992],{},"The list form of ",[131,978,979],{},"categories"," and ",[131,982,983],{},"values"," — sheet name, first row, first column, last row, last\ncolumn — is worth preferring over the string form because it takes the row count as a variable.\n",[131,986,987],{},"rows = len(monthly)"," is what stops the chart from silently excluding new months, which is the defect\nthat a hard-coded ",[131,990,991],{},"$B$2:$B$7"," produces the moment the data grows.",[119,994,996],{"id":995},"a-second-chart-from-a-different-tab","A second chart from a different tab",[124,998,1000],{"className":156,"code":999,"language":158,"meta":129,"style":129},"bar = book.add_chart({\"type\": \"column\"})\nbar.add_series({\n    \"name\":       \"Revenue by region\",\n    \"categories\": [\"Regional\", 1, 0, len(regional), 0],\n    \"values\":     [\"Regional\", 1, 1, len(regional), 1],\n    \"fill\":       {\"color\": \"#0F9488\"},\n    \"data_labels\": {\"value\": True, \"num_format\": \"#,##0\"},\n})\nbar.set_title({\"name\": \"Revenue by region\"})\nbar.set_legend({\"none\": True})\nbar.set_size({\"width\": 420, \"height\": 280})\ndashboard.insert_chart(\"K8\", bar)\n",[131,1001,1002,1020,1025,1036,1064,1090,1106,1130,1134,1147,1161,1183],{"__ignoreMap":129},[134,1003,1004,1007,1009,1011,1013,1015,1018],{"class":74,"line":136},[134,1005,1006],{"class":169},"bar ",[134,1008,192],{"class":165},[134,1010,660],{"class":169},[134,1012,663],{"class":143},[134,1014,666],{"class":169},[134,1016,1017],{"class":143},"\"column\"",[134,1019,316],{"class":169},[134,1021,1022],{"class":74,"line":179},[134,1023,1024],{"class":169},"bar.add_series({\n",[134,1026,1027,1029,1031,1034],{"class":74,"line":186},[134,1028,681],{"class":143},[134,1030,684],{"class":169},[134,1032,1033],{"class":143},"\"Revenue by region\"",[134,1035,690],{"class":169},[134,1037,1038,1040,1042,1044,1046,1048,1050,1052,1054,1057,1060,1062],{"class":74,"line":198},[134,1039,695],{"class":143},[134,1041,204],{"class":169},[134,1043,471],{"class":143},[134,1045,210],{"class":169},[134,1047,559],{"class":247},[134,1049,210],{"class":169},[134,1051,41],{"class":247},[134,1053,210],{"class":169},[134,1055,1056],{"class":247},"len",[134,1058,1059],{"class":169},"(regional), ",[134,1061,41],{"class":247},[134,1063,236],{"class":169},[134,1065,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088],{"class":74,"line":239},[134,1067,719],{"class":143},[134,1069,722],{"class":169},[134,1071,471],{"class":143},[134,1073,210],{"class":169},[134,1075,559],{"class":247},[134,1077,210],{"class":169},[134,1079,559],{"class":247},[134,1081,210],{"class":169},[134,1083,1056],{"class":247},[134,1085,1059],{"class":169},[134,1087,559],{"class":247},[134,1089,236],{"class":169},[134,1091,1092,1095,1097,1099,1101,1104],{"class":74,"line":278},[134,1093,1094],{"class":143},"    \"fill\"",[134,1096,746],{"class":169},[134,1098,749],{"class":143},[134,1100,666],{"class":169},[134,1102,1103],{"class":143},"\"#0F9488\"",[134,1105,767],{"class":169},[134,1107,1108,1111,1113,1116,1118,1120,1122,1124,1126,1128],{"class":74,"line":313},[134,1109,1110],{"class":143},"    \"data_labels\"",[134,1112,909],{"class":169},[134,1114,1115],{"class":143},"\"value\"",[134,1117,666],{"class":169},[134,1119,917],{"class":247},[134,1121,210],{"class":169},[134,1123,896],{"class":143},[134,1125,666],{"class":169},[134,1127,901],{"class":143},[134,1129,767],{"class":169},[134,1131,1132],{"class":74,"line":319},[134,1133,316],{"class":169},[134,1135,1136,1139,1141,1143,1145],{"class":74,"line":324},[134,1137,1138],{"class":169},"bar.set_title({",[134,1140,880],{"class":143},[134,1142,666],{"class":169},[134,1144,1033],{"class":143},[134,1146,316],{"class":169},[134,1148,1149,1152,1155,1157,1159],{"class":74,"line":334},[134,1150,1151],{"class":169},"bar.set_legend({",[134,1153,1154],{"class":143},"\"none\"",[134,1156,666],{"class":169},[134,1158,917],{"class":247},[134,1160,316],{"class":169},[134,1162,1163,1166,1168,1170,1173,1175,1177,1179,1181],{"class":74,"line":362},[134,1164,1165],{"class":169},"bar.set_size({",[134,1167,759],{"class":143},[134,1169,666],{"class":169},[134,1171,1172],{"class":247},"420",[134,1174,210],{"class":169},[134,1176,954],{"class":143},[134,1178,666],{"class":169},[134,1180,959],{"class":247},[134,1182,316],{"class":169},[134,1184,1185,1187,1190],{"class":74,"line":389},[134,1186,967],{"class":169},[134,1188,1189],{"class":143},"\"K8\"",[134,1191,1192],{"class":169},", bar)\n",[10,1194,1195],{},"Two charts side by side, each reading from its own tab, anchored to cells in the same row so their\ntops align. Anchoring to cells rather than pixel offsets is what keeps that alignment when a column\nwidth changes.",[119,1197,1199],{"id":1198},"laying-the-page-out-deliberately","Laying the page out deliberately",[124,1201,1203],{"className":156,"code":1202,"language":158,"meta":129,"style":129},"dashboard.hide_gridlines(2)\ndashboard.set_column(\"A:A\", 2)\ndashboard.set_column(\"B:T\", 9)\ndashboard.set_row(0, 30)\n\ntitle = book.add_format({\"font_size\": 16, \"bold\": True, \"font_color\": \"#172033\"})\nsubtitle = book.add_format({\"font_size\": 10, \"font_color\": \"#5B6780\"})\ndashboard.write(\"B1\", \"First half 2026 — performance\", title)\ndashboard.write(\"B2\", \"Generated automatically; figures from the Monthly and Regional tabs\", subtitle)\ndashboard.set_tab_color(\"#5B5CF0\")\n",[131,1204,1205,1214,1228,1242,1256,1260,1299,1325,1341,1356],{"__ignoreMap":129},[134,1206,1207,1210,1212],{"class":74,"line":136},[134,1208,1209],{"class":169},"dashboard.hide_gridlines(",[134,1211,580],{"class":247},[134,1213,459],{"class":169},[134,1215,1216,1219,1222,1224,1226],{"class":74,"line":179},[134,1217,1218],{"class":169},"dashboard.set_column(",[134,1220,1221],{"class":143},"\"A:A\"",[134,1223,210],{"class":169},[134,1225,580],{"class":247},[134,1227,459],{"class":169},[134,1229,1230,1232,1235,1237,1240],{"class":74,"line":186},[134,1231,1218],{"class":169},[134,1233,1234],{"class":143},"\"B:T\"",[134,1236,210],{"class":169},[134,1238,1239],{"class":247},"9",[134,1241,459],{"class":169},[134,1243,1244,1247,1249,1251,1254],{"class":74,"line":198},[134,1245,1246],{"class":169},"dashboard.set_row(",[134,1248,41],{"class":247},[134,1250,210],{"class":169},[134,1252,1253],{"class":247},"30",[134,1255,459],{"class":169},[134,1257,1258],{"class":74,"line":239},[134,1259,183],{"emptyLinePlaceholder":182},[134,1261,1262,1265,1267,1270,1273,1275,1278,1280,1283,1285,1287,1289,1292,1294,1297],{"class":74,"line":278},[134,1263,1264],{"class":169},"title ",[134,1266,192],{"class":165},[134,1268,1269],{"class":169}," book.add_format({",[134,1271,1272],{"class":143},"\"font_size\"",[134,1274,666],{"class":169},[134,1276,1277],{"class":247},"16",[134,1279,210],{"class":169},[134,1281,1282],{"class":143},"\"bold\"",[134,1284,666],{"class":169},[134,1286,917],{"class":247},[134,1288,210],{"class":169},[134,1290,1291],{"class":143},"\"font_color\"",[134,1293,666],{"class":169},[134,1295,1296],{"class":143},"\"#172033\"",[134,1298,316],{"class":169},[134,1300,1301,1304,1306,1308,1310,1312,1314,1316,1318,1320,1323],{"class":74,"line":313},[134,1302,1303],{"class":169},"subtitle ",[134,1305,192],{"class":165},[134,1307,1269],{"class":169},[134,1309,1272],{"class":143},[134,1311,666],{"class":169},[134,1313,549],{"class":247},[134,1315,210],{"class":169},[134,1317,1291],{"class":143},[134,1319,666],{"class":169},[134,1321,1322],{"class":143},"\"#5B6780\"",[134,1324,316],{"class":169},[134,1326,1327,1330,1333,1335,1338],{"class":74,"line":319},[134,1328,1329],{"class":169},"dashboard.write(",[134,1331,1332],{"class":143},"\"B1\"",[134,1334,210],{"class":169},[134,1336,1337],{"class":143},"\"First half 2026 — performance\"",[134,1339,1340],{"class":169},", title)\n",[134,1342,1343,1345,1348,1350,1353],{"class":74,"line":324},[134,1344,1329],{"class":169},[134,1346,1347],{"class":143},"\"B2\"",[134,1349,210],{"class":169},[134,1351,1352],{"class":143},"\"Generated automatically; figures from the Monthly and Regional tabs\"",[134,1354,1355],{"class":169},", subtitle)\n",[134,1357,1358,1361,1363],{"class":74,"line":334},[134,1359,1360],{"class":169},"dashboard.set_tab_color(",[134,1362,754],{"class":143},[134,1364,459],{"class":169},[10,1366,1367],{},"Uniform narrow columns give a grid that charts and tables can both align to, and a coloured tab makes\nthe dashboard identifiable in a workbook with a dozen sheets. Neither is cosmetic in the dismissive\nsense — a report that looks assembled is one people trust.",[119,1369,1371],{"id":1370},"the-openpyxl-version","The openpyxl version",[10,1373,1374,1375,1378],{},"For a workbook that already exists, openpyxl builds the same charts from ",[131,1376,1377],{},"Reference"," objects.",[124,1380,1382],{"className":156,"code":1381,"language":158,"meta":129,"style":129},"from openpyxl import load_workbook\nfrom openpyxl.chart import BarChart, LineChart, Reference\n\nbook = load_workbook(\"existing.xlsx\")\ndashboard = book[\"Dashboard\"] if \"Dashboard\" in book.sheetnames else book.create_sheet(\"Dashboard\", 0)\ndata_sheet = book[\"Monthly\"]\n\nchart = LineChart()\nchart.title = \"Revenue against target\"\nchart.height, chart.width = 8, 16\nvalues = Reference(data_sheet, min_col=2, max_col=3, min_row=1, max_row=data_sheet.max_row)\ncategories = Reference(data_sheet, min_col=1, min_row=2, max_row=data_sheet.max_row)\nchart.add_data(values, titles_from_data=True)\nchart.set_categories(categories)\ndashboard.add_chart(chart, \"B8\")\n\nbook.save(\"existing-with-dashboard.xlsx\")\n",[131,1383,1384,1397,1409,1413,1428,1469,1483,1487,1497,1507,1522,1567,1598,1612,1617,1626,1630],{"__ignoreMap":129},[134,1385,1386,1389,1392,1394],{"class":74,"line":136},[134,1387,1388],{"class":165},"from",[134,1390,1391],{"class":169}," openpyxl ",[134,1393,166],{"class":165},[134,1395,1396],{"class":169}," load_workbook\n",[134,1398,1399,1401,1404,1406],{"class":74,"line":179},[134,1400,1388],{"class":165},[134,1402,1403],{"class":169}," openpyxl.chart ",[134,1405,166],{"class":165},[134,1407,1408],{"class":169}," BarChart, LineChart, Reference\n",[134,1410,1411],{"class":74,"line":186},[134,1412,183],{"emptyLinePlaceholder":182},[134,1414,1415,1418,1420,1423,1426],{"class":74,"line":198},[134,1416,1417],{"class":169},"book ",[134,1419,192],{"class":165},[134,1421,1422],{"class":169}," load_workbook(",[134,1424,1425],{"class":143},"\"existing.xlsx\"",[134,1427,459],{"class":169},[134,1429,1430,1433,1435,1438,1440,1443,1446,1449,1452,1455,1458,1461,1463,1465,1467],{"class":74,"line":239},[134,1431,1432],{"class":169},"dashboard ",[134,1434,192],{"class":165},[134,1436,1437],{"class":169}," book[",[134,1439,504],{"class":143},[134,1441,1442],{"class":169},"] ",[134,1444,1445],{"class":165},"if",[134,1447,1448],{"class":143}," \"Dashboard\"",[134,1450,1451],{"class":165}," in",[134,1453,1454],{"class":169}," book.sheetnames ",[134,1456,1457],{"class":165},"else",[134,1459,1460],{"class":169}," book.create_sheet(",[134,1462,504],{"class":143},[134,1464,210],{"class":169},[134,1466,41],{"class":247},[134,1468,459],{"class":169},[134,1470,1471,1474,1476,1478,1480],{"class":74,"line":278},[134,1472,1473],{"class":169},"data_sheet ",[134,1475,192],{"class":165},[134,1477,1437],{"class":169},[134,1479,446],{"class":143},[134,1481,1482],{"class":169},"]\n",[134,1484,1485],{"class":74,"line":313},[134,1486,183],{"emptyLinePlaceholder":182},[134,1488,1489,1492,1494],{"class":74,"line":319},[134,1490,1491],{"class":169},"chart ",[134,1493,192],{"class":165},[134,1495,1496],{"class":169}," LineChart()\n",[134,1498,1499,1502,1504],{"class":74,"line":324},[134,1500,1501],{"class":169},"chart.title ",[134,1503,192],{"class":165},[134,1505,1506],{"class":143}," \"Revenue against target\"\n",[134,1508,1509,1512,1514,1517,1519],{"class":74,"line":334},[134,1510,1511],{"class":169},"chart.height, chart.width ",[134,1513,192],{"class":165},[134,1515,1516],{"class":247}," 8",[134,1518,210],{"class":169},[134,1520,1521],{"class":247},"16\n",[134,1523,1524,1527,1529,1532,1535,1537,1539,1541,1544,1546,1548,1550,1553,1555,1557,1559,1562,1564],{"class":74,"line":362},[134,1525,1526],{"class":169},"values ",[134,1528,192],{"class":165},[134,1530,1531],{"class":169}," Reference(data_sheet, ",[134,1533,1534],{"class":419},"min_col",[134,1536,192],{"class":165},[134,1538,580],{"class":247},[134,1540,210],{"class":169},[134,1542,1543],{"class":419},"max_col",[134,1545,192],{"class":165},[134,1547,598],{"class":247},[134,1549,210],{"class":169},[134,1551,1552],{"class":419},"min_row",[134,1554,192],{"class":165},[134,1556,559],{"class":247},[134,1558,210],{"class":169},[134,1560,1561],{"class":419},"max_row",[134,1563,192],{"class":165},[134,1565,1566],{"class":169},"data_sheet.max_row)\n",[134,1568,1569,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596],{"class":74,"line":389},[134,1570,1571],{"class":169},"categories ",[134,1573,192],{"class":165},[134,1575,1531],{"class":169},[134,1577,1534],{"class":419},[134,1579,192],{"class":165},[134,1581,559],{"class":247},[134,1583,210],{"class":169},[134,1585,1552],{"class":419},[134,1587,192],{"class":165},[134,1589,580],{"class":247},[134,1591,210],{"class":169},[134,1593,1561],{"class":419},[134,1595,192],{"class":165},[134,1597,1566],{"class":169},[134,1599,1600,1603,1606,1608,1610],{"class":74,"line":811},[134,1601,1602],{"class":169},"chart.add_data(values, ",[134,1604,1605],{"class":419},"titles_from_data",[134,1607,192],{"class":165},[134,1609,917],{"class":247},[134,1611,459],{"class":169},[134,1613,1614],{"class":74,"line":834},[134,1615,1616],{"class":169},"chart.set_categories(categories)\n",[134,1618,1619,1622,1624],{"class":74,"line":869},[134,1620,1621],{"class":169},"dashboard.add_chart(chart, ",[134,1623,970],{"class":143},[134,1625,459],{"class":169},[134,1627,1628],{"class":74,"line":874},[134,1629,183],{"emptyLinePlaceholder":182},[134,1631,1632,1635,1638],{"class":74,"line":890},[134,1633,1634],{"class":169},"book.save(",[134,1636,1637],{"class":143},"\"existing-with-dashboard.xlsx\"",[134,1639,459],{"class":169},[10,1641,1642,1644,1645,1648],{},[131,1643,1377],{}," takes the worksheet object as its first argument, which is how the cross-sheet reference\nis expressed — there is no sheet name string to get wrong. ",[131,1646,1647],{},"titles_from_data=True"," reads the series\nnames from the header row, so the legend matches the columns without being restated.",[119,1650,1652],{"id":1651},"combining-several-sources-into-one-chart","Combining several sources into one chart",[10,1654,1655],{},"Two series from two different tabs on the same chart is straightforward, because each series carries\nits own reference. What is not straightforward is a chart whose categories come from one tab and\nwhose values come from another — the axes have to agree, and nothing checks that they do.",[124,1657,1659],{"className":156,"code":1658,"language":158,"meta":129,"style":129},"combo = book.add_chart({\"type\": \"column\"})\ncombo.add_series({\n    \"name\":       \"This year\",\n    \"categories\": [\"Monthly\", 1, 0, rows, 0],\n    \"values\":     [\"Monthly\", 1, 1, rows, 1],\n    \"fill\":       {\"color\": \"#5B5CF0\"},\n})\ncombo.add_series({\n    \"name\":       \"Last year\",\n    \"categories\": [\"Monthly\", 1, 0, rows, 0],\n    \"values\":     [\"LastYear\", 1, 1, rows, 1],\n    \"fill\":       {\"color\": \"#CDD5E6\"},\n})\ncombo.set_title({\"name\": \"This year against last\"})\ndashboard.insert_chart(\"B26\", combo)\n",[131,1660,1661,1678,1683,1694,1716,1738,1752,1756,1760,1771,1793,1816,1831,1835,1849],{"__ignoreMap":129},[134,1662,1663,1666,1668,1670,1672,1674,1676],{"class":74,"line":136},[134,1664,1665],{"class":169},"combo ",[134,1667,192],{"class":165},[134,1669,660],{"class":169},[134,1671,663],{"class":143},[134,1673,666],{"class":169},[134,1675,1017],{"class":143},[134,1677,316],{"class":169},[134,1679,1680],{"class":74,"line":179},[134,1681,1682],{"class":169},"combo.add_series({\n",[134,1684,1685,1687,1689,1692],{"class":74,"line":186},[134,1686,681],{"class":143},[134,1688,684],{"class":169},[134,1690,1691],{"class":143},"\"This year\"",[134,1693,690],{"class":169},[134,1695,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714],{"class":74,"line":198},[134,1697,695],{"class":143},[134,1699,204],{"class":169},[134,1701,446],{"class":143},[134,1703,210],{"class":169},[134,1705,559],{"class":247},[134,1707,210],{"class":169},[134,1709,41],{"class":247},[134,1711,710],{"class":169},[134,1713,41],{"class":247},[134,1715,236],{"class":169},[134,1717,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736],{"class":74,"line":239},[134,1719,719],{"class":143},[134,1721,722],{"class":169},[134,1723,446],{"class":143},[134,1725,210],{"class":169},[134,1727,559],{"class":247},[134,1729,210],{"class":169},[134,1731,559],{"class":247},[134,1733,710],{"class":169},[134,1735,559],{"class":247},[134,1737,236],{"class":169},[134,1739,1740,1742,1744,1746,1748,1750],{"class":74,"line":278},[134,1741,1094],{"class":143},[134,1743,746],{"class":169},[134,1745,749],{"class":143},[134,1747,666],{"class":169},[134,1749,754],{"class":143},[134,1751,767],{"class":169},[134,1753,1754],{"class":74,"line":313},[134,1755,316],{"class":169},[134,1757,1758],{"class":74,"line":319},[134,1759,1682],{"class":169},[134,1761,1762,1764,1766,1769],{"class":74,"line":324},[134,1763,681],{"class":143},[134,1765,684],{"class":169},[134,1767,1768],{"class":143},"\"Last year\"",[134,1770,690],{"class":169},[134,1772,1773,1775,1777,1779,1781,1783,1785,1787,1789,1791],{"class":74,"line":334},[134,1774,695],{"class":143},[134,1776,204],{"class":169},[134,1778,446],{"class":143},[134,1780,210],{"class":169},[134,1782,559],{"class":247},[134,1784,210],{"class":169},[134,1786,41],{"class":247},[134,1788,710],{"class":169},[134,1790,41],{"class":247},[134,1792,236],{"class":169},[134,1794,1795,1797,1799,1802,1804,1806,1808,1810,1812,1814],{"class":74,"line":362},[134,1796,719],{"class":143},[134,1798,722],{"class":169},[134,1800,1801],{"class":143},"\"LastYear\"",[134,1803,210],{"class":169},[134,1805,559],{"class":247},[134,1807,210],{"class":169},[134,1809,559],{"class":247},[134,1811,710],{"class":169},[134,1813,559],{"class":247},[134,1815,236],{"class":169},[134,1817,1818,1820,1822,1824,1826,1829],{"class":74,"line":389},[134,1819,1094],{"class":143},[134,1821,746],{"class":169},[134,1823,749],{"class":143},[134,1825,666],{"class":169},[134,1827,1828],{"class":143},"\"#CDD5E6\"",[134,1830,767],{"class":169},[134,1832,1833],{"class":74,"line":811},[134,1834,316],{"class":169},[134,1836,1837,1840,1842,1844,1847],{"class":74,"line":834},[134,1838,1839],{"class":169},"combo.set_title({",[134,1841,880],{"class":143},[134,1843,666],{"class":169},[134,1845,1846],{"class":143},"\"This year against last\"",[134,1848,316],{"class":169},[134,1850,1851,1853,1856],{"class":74,"line":869},[134,1852,967],{"class":169},[134,1854,1855],{"class":143},"\"B26\"",[134,1857,1858],{"class":169},", combo)\n",[10,1860,1861],{},"Both series use the same categories deliberately: the months come from one tab, and the second\nseries' values are lined up against them by position rather than by matching month names. That works\nonly if the two tabs really do hold the same months in the same order, which is a property worth\nasserting in Python before writing either sheet.",[124,1863,1865],{"className":156,"code":1864,"language":158,"meta":129,"style":129},"assert list(monthly[\"Month\"]) == list(last_year[\"Month\"]), \"month axes do not match\"\n",[131,1866,1867],{"__ignoreMap":129},[134,1868,1869,1872,1875,1878,1881,1884,1887,1889,1892,1894,1897],{"class":74,"line":136},[134,1870,1871],{"class":165},"assert",[134,1873,1874],{"class":247}," list",[134,1876,1877],{"class":169},"(monthly[",[134,1879,1880],{"class":143},"\"Month\"",[134,1882,1883],{"class":169},"]) ",[134,1885,1886],{"class":165},"==",[134,1888,1874],{"class":247},[134,1890,1891],{"class":169},"(last_year[",[134,1893,1880],{"class":143},[134,1895,1896],{"class":169},"]), ",[134,1898,1899],{"class":143},"\"month axes do not match\"\n",[10,1901,1902,1903,1907],{},"One assertion prevents the class of dashboard bug where two lines are compared month by month and the\ncomparison is off by one — a defect that looks like a business insight rather than an error. The\nalignment problem itself, and the join that fixes it properly, is covered in\n",[14,1904,1906],{"href":1905},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002F","Running Totals and Year-Over-Year Growth in pandas",".",[119,1909,1911],{"id":1910},"making-the-tabs-behind-it-navigable","Making the tabs behind it navigable",[10,1913,1914],{},"A dashboard that points at six tabs benefits from pointing back. A row of hyperlinks under the charts\nturns the workbook into something a reader can explore rather than a page with hidden dependencies.",[124,1916,1918],{"className":156,"code":1917,"language":158,"meta":129,"style":129},"link = book.add_format({\"font_color\": \"#4338CA\", \"underline\": 1})\nfor index, (name, label) in enumerate([(\"Monthly\", \"Monthly detail\"),\n                                       (\"Regional\", \"Regional detail\")]):\n    dashboard.write_url(24, 1 + index * 4, f\"internal:'{name}'!A1\", link, label)\n",[131,1919,1920,1947,1974,1989],{"__ignoreMap":129},[134,1921,1922,1925,1927,1929,1931,1933,1936,1938,1941,1943,1945],{"class":74,"line":136},[134,1923,1924],{"class":169},"link ",[134,1926,192],{"class":165},[134,1928,1269],{"class":169},[134,1930,1291],{"class":143},[134,1932,666],{"class":169},[134,1934,1935],{"class":143},"\"#4338CA\"",[134,1937,210],{"class":169},[134,1939,1940],{"class":143},"\"underline\"",[134,1942,666],{"class":169},[134,1944,559],{"class":247},[134,1946,316],{"class":169},[134,1948,1949,1952,1955,1958,1961,1964,1966,1968,1971],{"class":74,"line":179},[134,1950,1951],{"class":165},"for",[134,1953,1954],{"class":169}," index, (name, label) ",[134,1956,1957],{"class":165},"in",[134,1959,1960],{"class":247}," enumerate",[134,1962,1963],{"class":169},"([(",[134,1965,446],{"class":143},[134,1967,210],{"class":169},[134,1969,1970],{"class":143},"\"Monthly detail\"",[134,1972,1973],{"class":169},"),\n",[134,1975,1976,1979,1981,1983,1986],{"class":74,"line":186},[134,1977,1978],{"class":169},"                                       (",[134,1980,471],{"class":143},[134,1982,210],{"class":169},[134,1984,1985],{"class":143},"\"Regional detail\"",[134,1987,1988],{"class":169},")]):\n",[134,1990,1991,1994,1997,1999,2001,2004,2007,2010,2013,2015,2018,2021,2025,2028,2031,2034],{"class":74,"line":198},[134,1992,1993],{"class":169},"    dashboard.write_url(",[134,1995,1996],{"class":247},"24",[134,1998,210],{"class":169},[134,2000,559],{"class":247},[134,2002,2003],{"class":165}," +",[134,2005,2006],{"class":169}," index ",[134,2008,2009],{"class":165},"*",[134,2011,2012],{"class":247}," 4",[134,2014,210],{"class":169},[134,2016,2017],{"class":165},"f",[134,2019,2020],{"class":143},"\"internal:'",[134,2022,2024],{"class":2023},"sSjpA","{",[134,2026,2027],{"class":169},"name",[134,2029,2030],{"class":2023},"}",[134,2032,2033],{"class":143},"'!A1\"",[134,2035,2036],{"class":169},", link, label)\n",[10,2038,2039,2042,2043,1907],{},[131,2040,2041],{},"internal:"," is the prefix that makes a link jump within the workbook rather than opening a browser,\nand quoting the sheet name handles the ones with spaces. Adding the reverse link — a \"back to\ndashboard\" cell at the top of each data tab — costs three more lines and is what makes a multi-sheet\nworkbook feel designed. The mechanics are covered in\n",[14,2044,2046],{"href":2045},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fadd-hyperlinks-to-excel-cells-with-python\u002F","Add Hyperlinks to Excel Cells with Python",[119,2048,2050],{"id":2049},"common-pitfalls","Common pitfalls",[2052,2053,2054,2070],"table",{},[2055,2056,2057],"thead",{},[2058,2059,2060,2064,2067],"tr",{},[2061,2062,2063],"th",{},"Symptom",[2061,2065,2066],{},"Cause",[2061,2068,2069],{},"Fix",[2071,2072,2073,2085,2100,2114,2125,2136],"tbody",{},[2058,2074,2075,2079,2082],{},[2076,2077,2078],"td",{},"Chart is blank",[2076,2080,2081],{},"Sheet name in the reference does not match exactly",[2076,2083,2084],{},"Names are case-sensitive; quote names containing spaces",[2058,2086,2087,2090,2093],{},[2076,2088,2089],{},"Recent rows missing from the chart",[2076,2091,2092],{},"Hard-coded range",[2076,2094,2095,2096,2099],{},"Size the reference from ",[131,2097,2098],{},"len(frame)"," or point at a table",[2058,2101,2102,2105,2108],{},[2076,2103,2104],{},"The header row appears as a data point",[2076,2106,2107],{},"Range starts at row 0",[2076,2109,2110,2111,2113],{},"Start values at row 1, and use ",[131,2112,1605],{}," for the name",[2058,2115,2116,2119,2122],{},[2076,2117,2118],{},"Charts overlap",[2076,2120,2121],{},"Anchored to the same cell, or sized larger than the gap",[2076,2123,2124],{},"Anchor to cells in the layout grid and set sizes explicitly",[2058,2126,2127,2130,2133],{},[2076,2128,2129],{},"Charts shift after editing the sheet",[2076,2131,2132],{},"Absolute pixel offsets",[2076,2134,2135],{},"Anchor to a cell; offsets move with it",[2058,2137,2138,2144,2147],{},[2076,2139,2140,2143],{},[131,2141,2142],{},"#REF!"," in the series after a save",[2076,2145,2146],{},"Rows were deleted rather than cleared",[2076,2148,2149],{},"Clear contents instead of deleting rows",[119,2151,2153],{"id":2152},"keeping-references-alive-as-data-grows","Keeping references alive as data grows",[10,2155,2156],{},"The most durable arrangement points each series at an Excel table rather than a range, because a\ntable grows with the rows written into it and every reference to it follows.",[124,2158,2160],{"className":156,"code":2159,"language":158,"meta":129,"style":129},"dashboard_data = writer.sheets[\"Monthly\"]\ndashboard_data.add_table(0, 0, len(monthly), 2, {\n    \"name\": \"MonthlyData\",\n    \"columns\": [{\"header\": c} for c in monthly.columns],\n    \"style\": \"Table Style Light 9\",\n})\n",[131,2161,2162,2176,2199,2210,2234,2246],{"__ignoreMap":129},[134,2163,2164,2167,2169,2172,2174],{"class":74,"line":136},[134,2165,2166],{"class":169},"dashboard_data ",[134,2168,192],{"class":165},[134,2170,2171],{"class":169}," writer.sheets[",[134,2173,446],{"class":143},[134,2175,1482],{"class":169},[134,2177,2178,2181,2183,2185,2187,2189,2191,2194,2196],{"class":74,"line":179},[134,2179,2180],{"class":169},"dashboard_data.add_table(",[134,2182,41],{"class":247},[134,2184,210],{"class":169},[134,2186,41],{"class":247},[134,2188,210],{"class":169},[134,2190,1056],{"class":247},[134,2192,2193],{"class":169},"(monthly), ",[134,2195,580],{"class":247},[134,2197,2198],{"class":169},", {\n",[134,2200,2201,2203,2205,2208],{"class":74,"line":186},[134,2202,681],{"class":143},[134,2204,666],{"class":169},[134,2206,2207],{"class":143},"\"MonthlyData\"",[134,2209,690],{"class":169},[134,2211,2212,2215,2218,2221,2224,2226,2229,2231],{"class":74,"line":198},[134,2213,2214],{"class":143},"    \"columns\"",[134,2216,2217],{"class":169},": [{",[134,2219,2220],{"class":143},"\"header\"",[134,2222,2223],{"class":169},": c} ",[134,2225,1951],{"class":165},[134,2227,2228],{"class":169}," c ",[134,2230,1957],{"class":165},[134,2232,2233],{"class":169}," monthly.columns],\n",[134,2235,2236,2239,2241,2244],{"class":74,"line":239},[134,2237,2238],{"class":143},"    \"style\"",[134,2240,666],{"class":169},[134,2242,2243],{"class":143},"\"Table Style Light 9\"",[134,2245,690],{"class":169},[134,2247,2248],{"class":74,"line":278},[134,2249,316],{"class":169},[10,2251,2252,2253,2256,2257,2261],{},"Once the range is a table, a chart series pointed at ",[131,2254,2255],{},"MonthlyData[Revenue]"," expands automatically the\nnext time the sheet is regenerated with more rows. That is the same mechanism described in\n",[14,2258,2260],{"href":2259},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","Create an Excel Table with Python",",\napplied to charting rather than filtering.",[119,2263,2265],{"id":2264},"performance-and-scale","Performance and scale",[20,2267,29,2273,29,2276,29,2279,29,2283,29,2289,29,2295,29,2303,29,2309,29,2313,29,2316,29,2322,29,2327,29,2331,29,2334,29,2337,29,2342,29,2346],{"viewBox":2268,"role":23,"ariaLabelledBy":2269,"xmlns":27,"style":2272},"0 0 720 196",[2270,2271],"dsh-open-t","dsh-open-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,2274,2275],{"id":2270},"What a reader waits for when the file opens",[35,2277,2278],{"id":2271},"Rendering time grows with the number of charts and the points in each series, so a dashboard with a few aggregated charts opens far faster than one plotting every daily row.",[39,2280],{"x":41,"y":41,"width":2281,"height":2282,"fill":44},"720","196",[46,2284,2288],{"x":2285,"y":2286,"style":2287},"20","56","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","30 charts, daily points",[39,2290],{"x":2291,"y":2292,"width":48,"height":2293,"rx":2294,"fill":59,"stroke":60},"200","40","26","6",[39,2296],{"x":2297,"y":2298,"width":2299,"height":1996,"rx":2300,"fill":2301,"stroke":2302},"201","41","378.0","5","#fee8f2","var(--accent,#d81b73)",[46,2304,2308],{"x":2305,"y":2306,"style":2307},"592.0","58","font-size:12px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:start","slow to open",[46,2310,2312],{"x":2285,"y":2311,"style":2287},"100","6 charts, daily points",[39,2314],{"x":2291,"y":2315,"width":48,"height":2293,"rx":2294,"fill":59,"stroke":60},"84",[39,2317],{"x":2297,"y":2318,"width":2319,"height":1996,"rx":2300,"fill":2320,"stroke":2321},"85","95.3","#fdefd8","var(--gold,#b4740a)",[46,2323,2326],{"x":2305,"y":2324,"style":2325},"102","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","acceptable",[46,2328,2330],{"x":2285,"y":2329,"style":2287},"144","6 charts, monthly points",[39,2332],{"x":2291,"y":2333,"width":48,"height":2293,"rx":2294,"fill":59,"stroke":60},"128",[39,2335],{"x":2297,"y":2336,"width":546,"height":1996,"rx":2300,"fill":103,"stroke":104},"129",[46,2338,2341],{"x":2305,"y":2339,"style":2340},"146","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","instant",[46,2343,2345],{"x":2285,"y":2285,"style":2344},"font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","relative cost",[46,2347,2350],{"x":2348,"y":2349,"style":116},"360.0","186","aggregate before charting; 36 points read better than 1,100",[10,2352,2353],{},"Charts are cheap to write and expensive to open. A workbook with thirty charts, each pointing at a\nfew thousand rows, takes noticeably longer for Excel to render than the same data with three charts —\nand the reader gains nothing from the other twenty-seven. The practical limits worth respecting are a\nhandful of charts per dashboard sheet and a few hundred points per series; beyond that the chart\nstops communicating before the file stops opening.",[10,2355,2356],{},"Where a series genuinely has tens of thousands of points, aggregate before charting rather than\nplotting everything: a daily series over three years is 1,100 points that read as noise, while the\nmonthly aggregate is 36 that read as a trend.",[119,2358,2360],{"id":2359},"conclusion","Conclusion",[10,2362,2363],{},"Write the data tabs first, derive every chart reference from the frame's length rather than typing a\nrange, and anchor charts to cells in a uniform column grid so the layout survives an edit. Put the\ndashboard sheet first, hide its gridlines, and keep the chart count low — the sheet exists so that\nnobody has to open the tabs behind it.",[119,2365,2367],{"id":2366},"frequently-asked-questions","Frequently asked questions",[10,2369,2370,2374],{},[2371,2372,2373],"strong",{},"Can a chart on one sheet reference data on another?","\nYes — that is the normal arrangement for a dashboard. In xlsxwriter the series categories and values take a '=SheetName!$A$2:$A$10' style reference, and in openpyxl the Reference object takes the worksheet as its first argument.",[10,2376,2377,2380],{},[2371,2378,2379],{},"How do I stop the chart breaking when the data grows?","\nSize the reference from the data you just wrote rather than hard-coding a range, or point the series at an Excel table, which grows with its rows. Hard-coded ranges are the single most common cause of a chart that quietly stops including recent data.",[10,2382,2383,2386],{},[2371,2384,2385],{},"Why is my chart blank when the file opens?","\nUsually the series reference names a sheet that does not exist under that exact name, or the range is off by the header row. Sheet names in references are case-sensitive and must be quoted if they contain a space.",[10,2388,2389,2392],{},[2371,2390,2391],{},"Can I position charts precisely?","\nIn xlsxwriter, insert_chart takes a cell anchor plus x and y offsets and a scale. Anchoring each chart to a known cell in the layout grid is more maintainable than offsets alone, because inserting a row moves everything together.",[119,2394,2396],{"id":2395},"related","Related",[2398,2399,2400,2407,2414,2421,2428],"ul",{},[2401,2402,2403,2404,2406],"li",{},"Up one level: ",[14,2405,17],{"href":16}," — the workbook structure this page sits in.",[2401,2408,2409,2413],{},[14,2410,2412],{"href":2411},"\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"," — the headline strip above these charts.",[2401,2415,2416,2420],{},[14,2417,2419],{"href":2418},"\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"," — the openpyxl charting API in depth.",[2401,2422,2423,2427],{},[14,2424,2426],{"href":2425},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002F","Add a Chart to an Excel File with XlsxWriter"," — series options, axes and styling.",[2401,2429,2430,2434],{},[14,2431,2433],{"href":2432},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file\u002F","Write Multiple DataFrames to One Excel File"," — creating the tabs the charts read from.",[2436,2437,2438],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":129,"searchDepth":179,"depth":179,"links":2440},[2441,2442,2443,2444,2445,2446,2447,2448,2449,2450,2451,2452,2453,2454],{"id":121,"depth":179,"text":122},{"id":394,"depth":179,"text":395},{"id":526,"depth":179,"text":527},{"id":995,"depth":179,"text":996},{"id":1198,"depth":179,"text":1199},{"id":1370,"depth":179,"text":1371},{"id":1651,"depth":179,"text":1652},{"id":1910,"depth":179,"text":1911},{"id":2049,"depth":179,"text":2050},{"id":2152,"depth":179,"text":2153},{"id":2264,"depth":179,"text":2265},{"id":2359,"depth":179,"text":2360},{"id":2366,"depth":179,"text":2367},{"id":2395,"depth":179,"text":2396},"2026-09-04","Assemble one dashboard page whose charts read from other sheets: cross-sheet series references sized from the data, cell-anchored layout, and the openpyxl equivalent.","md",[2459,2461,2463,2465],{"q":2373,"a":2460},"Yes — that is the normal arrangement for a dashboard. In xlsxwriter the series categories and values take a '=SheetName!$A$2:$A$10' style reference, and in openpyxl the Reference object takes the worksheet as its first argument.",{"q":2379,"a":2462},"Size the reference from the data you just wrote rather than hard-coding a range, or point the series at an Excel table, which grows with its rows. Hard-coded ranges are the single most common cause of a chart that quietly stops including recent data.",{"q":2385,"a":2464},"Usually the series reference names a sheet that does not exist under that exact name, or the range is off by the header row. Sheet names in references are case-sensitive and must be quoted if they contain a space.",{"q":2391,"a":2466},"In xlsxwriter, insert_chart takes a cell anchor plus x and y offsets and a scale. Anchoring each chart to a known cell in the layout grid is more maintainable than offsets alone, because inserting a row moves everything together.",{"breadcrumb":2468},[2469,2472,2475],{"name":2470,"item":2471},"Home","\u002F",{"name":2473,"item":2474},"Automating Reporting Workflows","\u002Fautomating-reporting-workflows\u002F",{"name":17,"item":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs",{"title":5,"description":2478},"Create an Excel dashboard sheet in Python with charts referencing other tabs — xlsxwriter series lists sized from the frame, cell anchoring, tables that grow, and openpyxl References.","build-a-dashboard-sheet-with-charts-from-multiple-tabs","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs\u002Findex","how-to","4DKqTAmEfiXTrdD1rD5otUWXWGWJUaKghPo-IDftS4E",[2484,2488],{"title":2485,"path":2486,"stem":2487,"children":-1},"Add a Summary Sheet to an Excel Report with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python\u002Findex",{"title":2433,"path":2489,"stem":2490,"children":-1},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file\u002Findex",1788710163138]