[{"data":1,"prerenderedAt":2290},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image":2256,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image":2265,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image":2270,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image":2271},{"id":4,"title":5,"body":6,"dateModified":2222,"datePublished":2222,"description":2223,"extension":2224,"faq":2225,"keyword":2234,"kind":2235,"meta":2236,"navigation":236,"path":2245,"seo":2246,"slug":2249,"stem":2250,"topics":2251,"type":2254,"__hash__":2255},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image\u002Findex.md","Export an Excel Chart as an Image with Python",{"type":7,"value":8,"toc":2210},"minimark",[9,19,166,171,199,214,456,460,466,675,690,694,697,907,910,1062,1065,1205,1209,1215,1361,1371,1375,1382,1741,1744,1748,1751,1948,1955,2086,2090,2132,2136,2145,2149,2155,2161,2167,2173,2177,2206],[10,11,12,13,18],"p",{},"Charts live in workbooks, but people want them elsewhere: in the body of the weekly email, on a slide, in a dashboard page or a PDF summary. openpyxl and XlsxWriter only write a chart's definition — series, colours, axes — and leave the drawing to whatever application opens the file. To get a picture, something has to render it. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002F","Inserting Images and Logos into Excel",", covers the three practical routes: let Excel render it through COM, let LibreOffice render it on a server, or redraw the same chart from the same data with matplotlib.",[20,21,29,30,29,34,29,38,29,46,29,56,29,67,29,73,29,78,29,82,29,85,29,90,29,99,29,104,29,109,29,113,29,118,29,120,29,123,29,127,29,130,29,135,29,139,29,142,29,144,29,147,29,151,29,154,29,159,29,163],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 238","img",[25,26],"ce-choose-t","ce-choose-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"Choosing how to turn a workbook chart into an image",[35,36,37],"desc",{"id":26},"On Windows with Excel installed, Chart.Export gives an exact rendering. On a server without Excel, headless LibreOffice renders the workbook to PNG or PDF, which is then cropped. When the image must look the same everywhere or be generated at scale, redraw the chart with matplotlib from the same data.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","238","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"163.5","16.0","432.9","75.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[57,58,60],"g",{"transform":59},"translate(368.0,29.0) scale(1.000)",[61,62],"path",{"d":63,"fill":64,"stroke":65,"style":66},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[68,69,72],"text",{"x":70,"y":51,"style":71},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Where does the export run, and how exact must it be?",[74,75],"line",{"x1":70,"y1":76,"x2":70,"y2":77,"stroke":54,"style":55},"91.5","111.5",[74,79],{"x1":80,"y1":77,"x2":81,"y2":77,"stroke":54,"style":55},"134.7","625.3",[74,83],{"x1":80,"y1":77,"x2":80,"y2":84,"stroke":54,"style":55},"157.5",[86,87],"polygon",{"points":88,"fill":89},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[39,91],{"x":92,"y":93,"width":94,"height":95,"rx":96,"fill":45,"stroke":97,"style":98},"66.4","121.5","136.5","21.0","10","var(--line,#cdd5e6)","stroke-width:1.2px",[68,100,103],{"x":80,"y":101,"style":102},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","Windows with Excel",[39,105],{"x":106,"y":48,"width":107,"height":108,"rx":52,"fill":53,"stroke":54,"style":55},"20.0","229.3","59.0",[68,110,112],{"x":80,"y":111,"style":71},"189.0","Chart.Export",[68,114,117],{"x":80,"y":115,"style":116},"208.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","exact rendering",[74,119],{"x1":70,"y1":77,"x2":70,"y2":84,"stroke":54,"style":55},[86,121],{"points":122,"fill":89},"380.0,161.5 375.0,152.5 385.0,152.5",[39,124],{"x":125,"y":93,"width":126,"height":95,"rx":96,"fill":45,"stroke":97,"style":98},"306.1","147.8",[68,128,129],{"x":70,"y":101,"style":102},"Linux server, no Excel",[39,131],{"x":132,"y":48,"width":107,"height":108,"rx":52,"fill":133,"stroke":134,"style":55},"265.3","#d9f4f1","var(--teal,#0f9488)",[68,136,138],{"x":70,"y":111,"style":137},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","headless LibreOffice",[68,140,141],{"x":70,"y":115,"style":116},"PNG or PDF, then crop",[74,143],{"x1":81,"y1":77,"x2":81,"y2":84,"stroke":54,"style":55},[86,145],{"points":146,"fill":89},"625.3,161.5 620.3,152.5 630.3,152.5",[39,148],{"x":149,"y":93,"width":150,"height":95,"rx":96,"fill":45,"stroke":97,"style":98},"541.7","167.3",[68,152,153],{"x":81,"y":101,"style":102},"many images, full control",[39,155],{"x":156,"y":48,"width":107,"height":108,"rx":52,"fill":157,"stroke":158,"style":55},"510.7","#fdefd8","var(--gold,#b4740a)",[68,160,162],{"x":81,"y":111,"style":161},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","redraw with matplotlib",[68,164,165],{"x":81,"y":115,"style":116},"same data",[167,168,170],"h2",{"id":169},"prerequisites","Prerequisites",[172,173,178],"pre",{"className":174,"code":175,"language":176,"meta":177,"style":177},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pillow\n","bash","",[179,180,181],"code",{"__ignoreMap":177},[182,183,185,189,193,196],"span",{"class":74,"line":184},1,[182,186,188],{"class":187},"sMTad","pip",[182,190,192],{"class":191},"srMev"," install",[182,194,195],{"class":191}," openpyxl",[182,197,198],{"class":191}," pillow\n",[10,200,201,202,205,206,209,210,213],{},"LibreOffice (",[179,203,204],{},"soffice",") for the server route, ",[179,207,208],{},"pdftoppm"," from poppler for high-resolution output, and ",[179,211,212],{},"pywin32"," plus Excel for the COM route. A workbook with data on one sheet:",[172,215,219],{"className":216,"code":217,"language":218,"meta":177,"style":177},"language-python shiki shiki-themes github-light github-dark-high-contrast","import subprocess\n\nfrom openpyxl import Workbook\nfrom openpyxl.chart import BarChart, Reference\nfrom PIL import Image, ImageChops\n\ndef build_chart(ws_data):\n    chart = BarChart()\n    chart.x_axis.delete = False  # Excel hides axes unless this is set\n    chart.y_axis.delete = False\n    chart.add_data(Reference(ws_data, min_col=2, min_row=1, max_row=5), titles_from_data=True)\n    chart.set_categories(Reference(ws_data, min_col=1, min_row=2, max_row=5))\n    chart.title = \"Revenue\"\n    chart.width, chart.height = 16, 9          # centimetres\n    return chart\n","python",[179,220,221,231,238,252,265,280,285,298,310,325,336,386,417,428,447],{"__ignoreMap":177},[182,222,223,227],{"class":74,"line":184},[182,224,226],{"class":225},"s-kum","import",[182,228,230],{"class":229},"skGVy"," subprocess\n",[182,232,234],{"class":74,"line":233},2,[182,235,237],{"emptyLinePlaceholder":236},true,"\n",[182,239,241,244,247,249],{"class":74,"line":240},3,[182,242,243],{"class":225},"from",[182,245,246],{"class":229}," openpyxl ",[182,248,226],{"class":225},[182,250,251],{"class":229}," Workbook\n",[182,253,255,257,260,262],{"class":74,"line":254},4,[182,256,243],{"class":225},[182,258,259],{"class":229}," openpyxl.chart ",[182,261,226],{"class":225},[182,263,264],{"class":229}," BarChart, Reference\n",[182,266,268,270,274,277],{"class":74,"line":267},5,[182,269,243],{"class":225},[182,271,273],{"class":272},"sP0c6"," PIL",[182,275,276],{"class":225}," import",[182,278,279],{"class":229}," Image, ImageChops\n",[182,281,283],{"class":74,"line":282},6,[182,284,237],{"emptyLinePlaceholder":236},[182,286,288,291,295],{"class":74,"line":287},7,[182,289,290],{"class":225},"def",[182,292,294],{"class":293},"s_Opv"," build_chart",[182,296,297],{"class":229},"(ws_data):\n",[182,299,301,304,307],{"class":74,"line":300},8,[182,302,303],{"class":229},"    chart ",[182,305,306],{"class":225},"=",[182,308,309],{"class":229}," BarChart()\n",[182,311,313,316,318,321],{"class":74,"line":312},9,[182,314,315],{"class":229},"    chart.x_axis.delete ",[182,317,306],{"class":225},[182,319,320],{"class":272}," False",[182,322,324],{"class":323},"s-wDw","  # Excel hides axes unless this is set\n",[182,326,328,331,333],{"class":74,"line":327},10,[182,329,330],{"class":229},"    chart.y_axis.delete ",[182,332,306],{"class":225},[182,334,335],{"class":272}," False\n",[182,337,339,342,346,348,351,354,357,359,362,364,367,369,372,375,378,380,383],{"class":74,"line":338},11,[182,340,341],{"class":229},"    chart.add_data(Reference(ws_data, ",[182,343,345],{"class":344},"sa561","min_col",[182,347,306],{"class":225},[182,349,350],{"class":272},"2",[182,352,353],{"class":229},", ",[182,355,356],{"class":344},"min_row",[182,358,306],{"class":225},[182,360,361],{"class":272},"1",[182,363,353],{"class":229},[182,365,366],{"class":344},"max_row",[182,368,306],{"class":225},[182,370,371],{"class":272},"5",[182,373,374],{"class":229},"), ",[182,376,377],{"class":344},"titles_from_data",[182,379,306],{"class":225},[182,381,382],{"class":272},"True",[182,384,385],{"class":229},")\n",[182,387,389,392,394,396,398,400,402,404,406,408,410,412,414],{"class":74,"line":388},12,[182,390,391],{"class":229},"    chart.set_categories(Reference(ws_data, ",[182,393,345],{"class":344},[182,395,306],{"class":225},[182,397,361],{"class":272},[182,399,353],{"class":229},[182,401,356],{"class":344},[182,403,306],{"class":225},[182,405,350],{"class":272},[182,407,353],{"class":229},[182,409,366],{"class":344},[182,411,306],{"class":225},[182,413,371],{"class":272},[182,415,416],{"class":229},"))\n",[182,418,420,423,425],{"class":74,"line":419},13,[182,421,422],{"class":229},"    chart.title ",[182,424,306],{"class":225},[182,426,427],{"class":191}," \"Revenue\"\n",[182,429,431,434,436,439,441,444],{"class":74,"line":430},14,[182,432,433],{"class":229},"    chart.width, chart.height ",[182,435,306],{"class":225},[182,437,438],{"class":272}," 16",[182,440,353],{"class":229},[182,442,443],{"class":272},"9",[182,445,446],{"class":323},"          # centimetres\n",[182,448,450,453],{"class":74,"line":449},15,[182,451,452],{"class":225},"    return",[182,454,455],{"class":229}," chart\n",[167,457,459],{"id":458},"route-1-excel-renders-it-windows","Route 1 — Excel renders it (Windows)",[10,461,462,463,465],{},"With Excel installed, ask it for the picture. ",[179,464,112],{}," writes the chart exactly as it appears in the workbook, fonts and theme included:",[172,467,469],{"className":216,"code":468,"language":218,"meta":177,"style":177},"import win32com.client as win32\n\nexcel = win32.DispatchEx(\"Excel.Application\")\nexcel.Visible = False\ntry:\n    wb = excel.Workbooks.Open(r\"C:\\reports\\sales.xlsx\", ReadOnly=True)\n    ws = wb.Worksheets(\"Summary\")\n    for i in range(1, ws.ChartObjects().Count + 1):\n        chart_object = ws.ChartObjects(i)\n        chart_object.Chart.Export(rf\"C:\\reports\\chart_{i}.png\")\n    wb.Close(SaveChanges=False)\nfinally:\n    excel.Quit()\n",[179,470,471,484,488,503,512,520,566,581,612,622,648,663,670],{"__ignoreMap":177},[182,472,473,475,478,481],{"class":74,"line":184},[182,474,226],{"class":225},[182,476,477],{"class":229}," win32com.client ",[182,479,480],{"class":225},"as",[182,482,483],{"class":229}," win32\n",[182,485,486],{"class":74,"line":233},[182,487,237],{"emptyLinePlaceholder":236},[182,489,490,493,495,498,501],{"class":74,"line":240},[182,491,492],{"class":229},"excel ",[182,494,306],{"class":225},[182,496,497],{"class":229}," win32.DispatchEx(",[182,499,500],{"class":191},"\"Excel.Application\"",[182,502,385],{"class":229},[182,504,505,508,510],{"class":74,"line":254},[182,506,507],{"class":229},"excel.Visible ",[182,509,306],{"class":225},[182,511,335],{"class":272},[182,513,514,517],{"class":74,"line":267},[182,515,516],{"class":225},"try",[182,518,519],{"class":229},":\n",[182,521,522,525,527,530,533,536,540,543,546,549,552,555,557,560,562,564],{"class":74,"line":282},[182,523,524],{"class":229},"    wb ",[182,526,306],{"class":225},[182,528,529],{"class":229}," excel.Workbooks.Open(",[182,531,532],{"class":225},"r",[182,534,535],{"class":191},"\"C:",[182,537,539],{"class":538},"s_b0D","\\r",[182,541,542],{"class":191},"eports",[182,544,545],{"class":272},"\\s",[182,547,548],{"class":191},"ales",[182,550,551],{"class":272},".",[182,553,554],{"class":191},"xlsx\"",[182,556,353],{"class":229},[182,558,559],{"class":344},"ReadOnly",[182,561,306],{"class":225},[182,563,382],{"class":272},[182,565,385],{"class":229},[182,567,568,571,573,576,579],{"class":74,"line":287},[182,569,570],{"class":229},"    ws ",[182,572,306],{"class":225},[182,574,575],{"class":229}," wb.Worksheets(",[182,577,578],{"class":191},"\"Summary\"",[182,580,385],{"class":229},[182,582,583,586,589,592,595,598,600,603,606,609],{"class":74,"line":300},[182,584,585],{"class":225},"    for",[182,587,588],{"class":229}," i ",[182,590,591],{"class":225},"in",[182,593,594],{"class":272}," range",[182,596,597],{"class":229},"(",[182,599,361],{"class":272},[182,601,602],{"class":229},", ws.ChartObjects().Count ",[182,604,605],{"class":225},"+",[182,607,608],{"class":272}," 1",[182,610,611],{"class":229},"):\n",[182,613,614,617,619],{"class":74,"line":312},[182,615,616],{"class":229},"        chart_object ",[182,618,306],{"class":225},[182,620,621],{"class":229}," ws.ChartObjects(i)\n",[182,623,624,627,630,633,637,640,643,646],{"class":74,"line":327},[182,625,626],{"class":229},"        chart_object.Chart.Export(",[182,628,629],{"class":225},"rf",[182,631,632],{"class":191},"\"C:\\reports\\chart_",[182,634,636],{"class":635},"sSjpA","{",[182,638,639],{"class":229},"i",[182,641,642],{"class":635},"}",[182,644,645],{"class":191},".png\"",[182,647,385],{"class":229},[182,649,650,653,656,658,661],{"class":74,"line":338},[182,651,652],{"class":229},"    wb.Close(",[182,654,655],{"class":344},"SaveChanges",[182,657,306],{"class":225},[182,659,660],{"class":272},"False",[182,662,385],{"class":229},[182,664,665,668],{"class":74,"line":388},[182,666,667],{"class":225},"finally",[182,669,519],{"class":229},[182,671,672],{"class":74,"line":419},[182,673,674],{"class":229},"    excel.Quit()\n",[10,676,677,678,681,682,685,686,551],{},"Paths must be absolute, because Excel resolves them from its own working directory. The image size follows the chart object's size on the sheet; enlarge ",[179,679,680],{},"chart_object.Width"," and ",[179,683,684],{},"Height"," before exporting for a larger picture. Driving Excel through COM, including running it from scheduled tasks, is covered in ",[14,687,689],{"href":688},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-com-and-pywin32\u002Frun-excel-com-automation-from-task-scheduler\u002F","Run Excel COM Automation from Task Scheduler",[167,691,693],{"id":692},"route-2-libreoffice-renders-it-any-server","Route 2 — LibreOffice renders it (any server)",[10,695,696],{},"LibreOffice has no \"export this chart\" command, but it can render the first sheet of a workbook to PNG or PDF. So build a small workbook whose first sheet holds nothing but the chart, anchored at A1, with its data on a second sheet:",[172,698,700],{"className":216,"code":699,"language":218,"meta":177,"style":177},"wb = Workbook()\nchart_sheet = wb.active\nchart_sheet.title = \"ChartOnly\"\ndata = wb.create_sheet(\"Data\")\ndata.append([\"Month\", \"Revenue\"])\nfor row in [(\"Jan\", 82), (\"Feb\", 91), (\"Mar\", 78), (\"Apr\", 104)]:\n    data.append(row)\nchart_sheet.add_chart(build_chart(data), \"A1\")\nwb.save(\"chart_only.xlsx\")\n\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"png\", \"chart_only.xlsx\"],\n               check=True, capture_output=True, timeout=120)\n",[179,701,702,712,722,732,747,763,818,823,833,843,847,877],{"__ignoreMap":177},[182,703,704,707,709],{"class":74,"line":184},[182,705,706],{"class":229},"wb ",[182,708,306],{"class":225},[182,710,711],{"class":229}," Workbook()\n",[182,713,714,717,719],{"class":74,"line":233},[182,715,716],{"class":229},"chart_sheet ",[182,718,306],{"class":225},[182,720,721],{"class":229}," wb.active\n",[182,723,724,727,729],{"class":74,"line":240},[182,725,726],{"class":229},"chart_sheet.title ",[182,728,306],{"class":225},[182,730,731],{"class":191}," \"ChartOnly\"\n",[182,733,734,737,739,742,745],{"class":74,"line":254},[182,735,736],{"class":229},"data ",[182,738,306],{"class":225},[182,740,741],{"class":229}," wb.create_sheet(",[182,743,744],{"class":191},"\"Data\"",[182,746,385],{"class":229},[182,748,749,752,755,757,760],{"class":74,"line":267},[182,750,751],{"class":229},"data.append([",[182,753,754],{"class":191},"\"Month\"",[182,756,353],{"class":229},[182,758,759],{"class":191},"\"Revenue\"",[182,761,762],{"class":229},"])\n",[182,764,765,768,771,773,776,779,781,784,787,790,792,795,797,800,802,805,807,810,812,815],{"class":74,"line":282},[182,766,767],{"class":225},"for",[182,769,770],{"class":229}," row ",[182,772,591],{"class":225},[182,774,775],{"class":229}," [(",[182,777,778],{"class":191},"\"Jan\"",[182,780,353],{"class":229},[182,782,783],{"class":272},"82",[182,785,786],{"class":229},"), (",[182,788,789],{"class":191},"\"Feb\"",[182,791,353],{"class":229},[182,793,794],{"class":272},"91",[182,796,786],{"class":229},[182,798,799],{"class":191},"\"Mar\"",[182,801,353],{"class":229},[182,803,804],{"class":272},"78",[182,806,786],{"class":229},[182,808,809],{"class":191},"\"Apr\"",[182,811,353],{"class":229},[182,813,814],{"class":272},"104",[182,816,817],{"class":229},")]:\n",[182,819,820],{"class":74,"line":287},[182,821,822],{"class":229},"    data.append(row)\n",[182,824,825,828,831],{"class":74,"line":300},[182,826,827],{"class":229},"chart_sheet.add_chart(build_chart(data), ",[182,829,830],{"class":191},"\"A1\"",[182,832,385],{"class":229},[182,834,835,838,841],{"class":74,"line":312},[182,836,837],{"class":229},"wb.save(",[182,839,840],{"class":191},"\"chart_only.xlsx\"",[182,842,385],{"class":229},[182,844,845],{"class":74,"line":327},[182,846,237],{"emptyLinePlaceholder":236},[182,848,849,852,855,857,860,862,865,867,870,872,874],{"class":74,"line":338},[182,850,851],{"class":229},"subprocess.run([",[182,853,854],{"class":191},"\"soffice\"",[182,856,353],{"class":229},[182,858,859],{"class":191},"\"--headless\"",[182,861,353],{"class":229},[182,863,864],{"class":191},"\"--convert-to\"",[182,866,353],{"class":229},[182,868,869],{"class":191},"\"png\"",[182,871,353],{"class":229},[182,873,840],{"class":191},[182,875,876],{"class":229},"],\n",[182,878,879,882,884,886,888,891,893,895,897,900,902,905],{"class":74,"line":388},[182,880,881],{"class":344},"               check",[182,883,306],{"class":225},[182,885,382],{"class":272},[182,887,353],{"class":229},[182,889,890],{"class":344},"capture_output",[182,892,306],{"class":225},[182,894,382],{"class":272},[182,896,353],{"class":229},[182,898,899],{"class":344},"timeout",[182,901,306],{"class":225},[182,903,904],{"class":272},"120",[182,906,385],{"class":229},[10,908,909],{},"The PNG is a whole page: the chart in the top-left corner and white space around it. Crop it to the chart with Pillow by finding the bounding box of everything that is not white:",[172,911,913],{"className":216,"code":912,"language":218,"meta":177,"style":177},"def crop_white(path, out, margin=4):\n    picture = Image.open(path).convert(\"RGB\")\n    background = Image.new(\"RGB\", picture.size, (255, 255, 255))\n    left, top, right, bottom = ImageChops.difference(picture, background).getbbox()\n    picture.crop((max(left - margin, 0), max(top - margin, 0), right + margin, bottom + margin)).save(out)\n    return Image.open(out).size\n\nprint(crop_white(\"chart_only.png\", \"chart.png\"))        # (614, 349) on our system\n",[179,914,915,931,946,974,984,1029,1036,1040],{"__ignoreMap":177},[182,916,917,919,922,925,927,929],{"class":74,"line":184},[182,918,290],{"class":225},[182,920,921],{"class":293}," crop_white",[182,923,924],{"class":229},"(path, out, margin",[182,926,306],{"class":225},[182,928,41],{"class":272},[182,930,611],{"class":229},[182,932,933,936,938,941,944],{"class":74,"line":233},[182,934,935],{"class":229},"    picture ",[182,937,306],{"class":225},[182,939,940],{"class":229}," Image.open(path).convert(",[182,942,943],{"class":191},"\"RGB\"",[182,945,385],{"class":229},[182,947,948,951,953,956,958,961,964,966,968,970,972],{"class":74,"line":240},[182,949,950],{"class":229},"    background ",[182,952,306],{"class":225},[182,954,955],{"class":229}," Image.new(",[182,957,943],{"class":191},[182,959,960],{"class":229},", picture.size, (",[182,962,963],{"class":272},"255",[182,965,353],{"class":229},[182,967,963],{"class":272},[182,969,353],{"class":229},[182,971,963],{"class":272},[182,973,416],{"class":229},[182,975,976,979,981],{"class":74,"line":254},[182,977,978],{"class":229},"    left, top, right, bottom ",[182,980,306],{"class":225},[182,982,983],{"class":229}," ImageChops.difference(picture, background).getbbox()\n",[182,985,986,989,992,995,998,1001,1003,1005,1007,1010,1012,1014,1016,1019,1021,1024,1026],{"class":74,"line":267},[182,987,988],{"class":229},"    picture.crop((",[182,990,991],{"class":272},"max",[182,993,994],{"class":229},"(left ",[182,996,997],{"class":225},"-",[182,999,1000],{"class":229}," margin, ",[182,1002,42],{"class":272},[182,1004,374],{"class":229},[182,1006,991],{"class":272},[182,1008,1009],{"class":229},"(top ",[182,1011,997],{"class":225},[182,1013,1000],{"class":229},[182,1015,42],{"class":272},[182,1017,1018],{"class":229},"), right ",[182,1020,605],{"class":225},[182,1022,1023],{"class":229}," margin, bottom ",[182,1025,605],{"class":225},[182,1027,1028],{"class":229}," margin)).save(out)\n",[182,1030,1031,1033],{"class":74,"line":282},[182,1032,452],{"class":225},[182,1034,1035],{"class":229}," Image.open(out).size\n",[182,1037,1038],{"class":74,"line":287},[182,1039,237],{"emptyLinePlaceholder":236},[182,1041,1042,1045,1048,1051,1053,1056,1059],{"class":74,"line":300},[182,1043,1044],{"class":272},"print",[182,1046,1047],{"class":229},"(crop_white(",[182,1049,1050],{"class":191},"\"chart_only.png\"",[182,1052,353],{"class":229},[182,1054,1055],{"class":191},"\"chart.png\"",[182,1057,1058],{"class":229},"))        ",[182,1060,1061],{"class":323},"# (614, 349) on our system\n",[10,1063,1064],{},"On our system the page rendered at screen resolution and the cropped chart came out at roughly 600 × 340 pixels — fine for an email, soft on a slide. A chart on a chartsheet, by contrast, rendered as a blank page, which is why the chart goes on an ordinary worksheet here.",[20,1066,29,1071,29,1074,29,1077,29,1080,29,1084,29,1090,29,1094,29,1098,29,1102,29,1107,29,1110,29,1113,29,1120,29,1123,29,1126,29,1129,29,1133,29,1136,29,1139,29,1144,29,1147,29,1150,29,1153,29,1157,29,1160,29,1163,29,1170,29,1174,29,1177,29,1180,29,1184,29,1187,29,1190,29,1196,29,1201],{"viewBox":1067,"role":23,"ariaLabelledBy":1068,"xmlns":27,"style":28},"4 0 752 142",[1069,1070],"ce-lo-t","ce-lo-d",[31,1072,1073],{"id":1069},"Rendering a chart on a server with LibreOffice",[35,1075,1076],{"id":1070},"Build a workbook whose first sheet contains only the chart at A1, with its data on another sheet. Convert it with headless LibreOffice to PNG for a quick image or to PDF for high resolution, rasterise the PDF with pdftoppm, then crop the white page margins with Pillow.",[39,1078],{"x":41,"y":42,"width":43,"height":1079,"fill":45},"142",[39,1081],{"x":106,"y":49,"width":1082,"height":1083,"rx":52,"fill":53,"stroke":54,"style":55},"115.2","110.5",[57,1085,1087],{"transform":1086},"translate(65.6,29.0) scale(1.000)",[61,1088],{"d":1089,"fill":64,"stroke":65,"style":66},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[68,1091,1093],{"x":1092,"y":51,"style":71},"77.6","Chart-only",[68,1095,1097],{"x":1092,"y":1096,"style":71},"93.0","sheet",[68,1099,1101],{"x":1092,"y":1100,"style":116},"112.5","chart at A1",[74,1103],{"x1":1104,"y1":1105,"x2":1106,"y2":1105,"stroke":54,"style":55},"140.2","71.2","162.2",[86,1108],{"points":1109,"fill":89},"166.2,71.2 157.2,76.2 157.2,66.2",[39,1111],{"x":1112,"y":49,"width":1082,"height":1083,"rx":52,"fill":133,"stroke":134,"style":55},"171.2",[57,1114,1116],{"transform":1115},"translate(216.8,29.0) scale(1.000)",[61,1117],{"d":1118,"fill":64,"stroke":1119,"style":66},"M4 5h16v14H4z M7 9l3 3-3 3 M12 15h5","var(--teal-ink,#0b6157)",[68,1121,204],{"x":1122,"y":51,"style":137},"228.8",[68,1124,1125],{"x":1122,"y":1096,"style":137},"--headless",[68,1127,1128],{"x":1122,"y":1100,"style":116},"PNG or PDF",[74,1130],{"x1":1131,"y1":1105,"x2":1132,"y2":1105,"stroke":54,"style":55},"291.4","313.4",[86,1134],{"points":1135,"fill":89},"317.4,71.2 308.4,76.2 308.4,66.2",[39,1137],{"x":1138,"y":49,"width":1082,"height":1083,"rx":52,"fill":53,"stroke":54,"style":55},"322.4",[57,1140,1141],{"transform":59},[61,1142],{"d":1143,"fill":64,"stroke":65,"style":66},"M4 5h16v14H4z M4 16l5-5 4 4 3-3 4 4 M15 9h.01",[68,1145,1146],{"x":70,"y":51,"style":71},"pdftoppm -r",[68,1148,1149],{"x":70,"y":1096,"style":71},"200",[68,1151,1152],{"x":70,"y":1100,"style":116},"for sharp output",[74,1154],{"x1":1155,"y1":1105,"x2":1156,"y2":1105,"stroke":54,"style":55},"442.6","464.6",[86,1158],{"points":1159,"fill":89},"468.6,71.2 459.6,76.2 459.6,66.2",[39,1161],{"x":1162,"y":49,"width":1082,"height":1083,"rx":52,"fill":157,"stroke":158,"style":55},"473.6",[57,1164,1166],{"transform":1165},"translate(519.2,29.0) scale(1.000)",[61,1167],{"d":1168,"fill":64,"stroke":1169,"style":66},"M3 5h18l-7 8v6l-4 2v-8z","var(--gold-ink,#7a4e06)",[68,1171,1173],{"x":1172,"y":51,"style":161},"531.2","Crop",[68,1175,1176],{"x":1172,"y":1096,"style":161},"margins",[68,1178,1179],{"x":1172,"y":1100,"style":116},"Pillow getbbox",[74,1181],{"x1":1182,"y1":1105,"x2":1183,"y2":1105,"stroke":54,"style":55},"593.8","615.8",[86,1185],{"points":1186,"fill":89},"619.8,71.2 610.8,76.2 610.8,66.2",[39,1188],{"x":1189,"y":49,"width":1082,"height":1083,"rx":52,"fill":53,"stroke":54,"style":55},"624.8",[57,1191,1193],{"transform":1192},"translate(670.4,37.8) scale(1.000)",[61,1194],{"d":1195,"fill":64,"stroke":65,"style":66},"M5 12.5l4.5 4.5L19 7",[68,1197,1200],{"x":1198,"y":1199,"style":71},"682.4","84.2","chart.png",[68,1202,1204],{"x":1198,"y":1203,"style":116},"103.8","ready to use",[167,1206,1208],{"id":1207},"route-2b-higher-resolution-through-pdf","Route 2b — Higher resolution through PDF",[10,1210,1211,1212,1214],{},"For slides and print, render to PDF and rasterise at the resolution you need with ",[179,1213,208],{},", then crop as before:",[172,1216,1218],{"className":216,"code":1217,"language":218,"meta":177,"style":177},"subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"pdf\", \"chart_only.xlsx\"],\n               check=True, capture_output=True, timeout=120)\nsubprocess.run([\"pdftoppm\", \"-f\", \"1\", \"-l\", \"1\", \"-r\", \"200\", \"-png\", \"chart_only.pdf\", \"page\"],\n               check=True, timeout=120)\nprint(crop_white(\"page-1.png\", \"chart_hd.png\"))        # (1268, 717)\n",[179,1219,1220,1245,1271,1324,1342],{"__ignoreMap":177},[182,1221,1222,1224,1226,1228,1230,1232,1234,1236,1239,1241,1243],{"class":74,"line":184},[182,1223,851],{"class":229},[182,1225,854],{"class":191},[182,1227,353],{"class":229},[182,1229,859],{"class":191},[182,1231,353],{"class":229},[182,1233,864],{"class":191},[182,1235,353],{"class":229},[182,1237,1238],{"class":191},"\"pdf\"",[182,1240,353],{"class":229},[182,1242,840],{"class":191},[182,1244,876],{"class":229},[182,1246,1247,1249,1251,1253,1255,1257,1259,1261,1263,1265,1267,1269],{"class":74,"line":233},[182,1248,881],{"class":344},[182,1250,306],{"class":225},[182,1252,382],{"class":272},[182,1254,353],{"class":229},[182,1256,890],{"class":344},[182,1258,306],{"class":225},[182,1260,382],{"class":272},[182,1262,353],{"class":229},[182,1264,899],{"class":344},[182,1266,306],{"class":225},[182,1268,904],{"class":272},[182,1270,385],{"class":229},[182,1272,1273,1275,1278,1280,1283,1285,1288,1290,1293,1295,1297,1299,1302,1304,1307,1309,1312,1314,1317,1319,1322],{"class":74,"line":240},[182,1274,851],{"class":229},[182,1276,1277],{"class":191},"\"pdftoppm\"",[182,1279,353],{"class":229},[182,1281,1282],{"class":191},"\"-f\"",[182,1284,353],{"class":229},[182,1286,1287],{"class":191},"\"1\"",[182,1289,353],{"class":229},[182,1291,1292],{"class":191},"\"-l\"",[182,1294,353],{"class":229},[182,1296,1287],{"class":191},[182,1298,353],{"class":229},[182,1300,1301],{"class":191},"\"-r\"",[182,1303,353],{"class":229},[182,1305,1306],{"class":191},"\"200\"",[182,1308,353],{"class":229},[182,1310,1311],{"class":191},"\"-png\"",[182,1313,353],{"class":229},[182,1315,1316],{"class":191},"\"chart_only.pdf\"",[182,1318,353],{"class":229},[182,1320,1321],{"class":191},"\"page\"",[182,1323,876],{"class":229},[182,1325,1326,1328,1330,1332,1334,1336,1338,1340],{"class":74,"line":254},[182,1327,881],{"class":344},[182,1329,306],{"class":225},[182,1331,382],{"class":272},[182,1333,353],{"class":229},[182,1335,899],{"class":344},[182,1337,306],{"class":225},[182,1339,904],{"class":272},[182,1341,385],{"class":229},[182,1343,1344,1346,1348,1351,1353,1356,1358],{"class":74,"line":267},[182,1345,1044],{"class":272},[182,1347,1047],{"class":229},[182,1349,1350],{"class":191},"\"page-1.png\"",[182,1352,353],{"class":229},[182,1354,1355],{"class":191},"\"chart_hd.png\"",[182,1357,1058],{"class":229},[182,1359,1360],{"class":323},"# (1268, 717)\n",[10,1362,1363,1364,1366,1367,1370],{},"At 200 dots per inch the same chart comes out at about 1,250 × 700 pixels with crisp text, because the PDF keeps the chart as vector graphics until ",[179,1365,208],{}," draws it. Set ",[179,1368,1369],{},"-r 300"," for print; the rendering time grows with the resolution, but for a single chart it stays well under a second. The PDF itself is also a good deliverable when the chart goes into a document: it scales without losing quality.",[167,1372,1374],{"id":1373},"route-2c-every-chart-in-an-existing-workbook","Route 2c — Every chart in an existing workbook",[10,1376,1377,1378,1381],{},"Reports usually arrive with several charts already on a sheet. openpyxl reads existing charts back into ",[179,1379,1380],{},"ws._charts",", so each one can be moved onto its own render page: load the workbook, take one chart off its sheet, anchor it at A1 of a new first sheet, save a copy and render that:",[172,1383,1385],{"className":216,"code":1384,"language":218,"meta":177,"style":177},"from openpyxl import load_workbook\n\ndef export_charts(path, sheet_name, prefix=\"chart\", size_cm=(16, 9)):\n    count = len(load_workbook(path)[sheet_name]._charts)\n    sizes = []\n    for i in range(count):\n        wb = load_workbook(path)\n        chart = wb[sheet_name]._charts[i]\n        wb[sheet_name]._charts = []                 # keep only the chart being exported\n        chart.width, chart.height = size_cm         # reloaded charts use the default size\n        page = wb.create_sheet(\"Render\", 0)\n        page.add_chart(chart, \"A1\")\n        wb.active = 0\n        wb.save(f\"{prefix}_{i}.xlsx\")\n        subprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"png\", f\"{prefix}_{i}.xlsx\"],\n                       check=True, capture_output=True, timeout=120)\n        sizes.append(crop_white(f\"{prefix}_{i}.png\", f\"{prefix}_{i}_cropped.png\"))\n    return sizes\n\nprint(export_charts(\"chart_only.xlsx\", \"ChartOnly\"))     # [(614, 349)]\n",[179,1386,1387,1398,1402,1434,1447,1457,1470,1480,1490,1503,1516,1534,1543,1553,1585,1628,1656,1707,1715,1720],{"__ignoreMap":177},[182,1388,1389,1391,1393,1395],{"class":74,"line":184},[182,1390,243],{"class":225},[182,1392,246],{"class":229},[182,1394,226],{"class":225},[182,1396,1397],{"class":229}," load_workbook\n",[182,1399,1400],{"class":74,"line":233},[182,1401,237],{"emptyLinePlaceholder":236},[182,1403,1404,1406,1409,1412,1414,1417,1420,1422,1424,1427,1429,1431],{"class":74,"line":240},[182,1405,290],{"class":225},[182,1407,1408],{"class":293}," export_charts",[182,1410,1411],{"class":229},"(path, sheet_name, prefix",[182,1413,306],{"class":225},[182,1415,1416],{"class":191},"\"chart\"",[182,1418,1419],{"class":229},", size_cm",[182,1421,306],{"class":225},[182,1423,597],{"class":229},[182,1425,1426],{"class":272},"16",[182,1428,353],{"class":229},[182,1430,443],{"class":272},[182,1432,1433],{"class":229},")):\n",[182,1435,1436,1439,1441,1444],{"class":74,"line":254},[182,1437,1438],{"class":229},"    count ",[182,1440,306],{"class":225},[182,1442,1443],{"class":272}," len",[182,1445,1446],{"class":229},"(load_workbook(path)[sheet_name]._charts)\n",[182,1448,1449,1452,1454],{"class":74,"line":267},[182,1450,1451],{"class":229},"    sizes ",[182,1453,306],{"class":225},[182,1455,1456],{"class":229}," []\n",[182,1458,1459,1461,1463,1465,1467],{"class":74,"line":282},[182,1460,585],{"class":225},[182,1462,588],{"class":229},[182,1464,591],{"class":225},[182,1466,594],{"class":272},[182,1468,1469],{"class":229},"(count):\n",[182,1471,1472,1475,1477],{"class":74,"line":287},[182,1473,1474],{"class":229},"        wb ",[182,1476,306],{"class":225},[182,1478,1479],{"class":229}," load_workbook(path)\n",[182,1481,1482,1485,1487],{"class":74,"line":300},[182,1483,1484],{"class":229},"        chart ",[182,1486,306],{"class":225},[182,1488,1489],{"class":229}," wb[sheet_name]._charts[i]\n",[182,1491,1492,1495,1497,1500],{"class":74,"line":312},[182,1493,1494],{"class":229},"        wb[sheet_name]._charts ",[182,1496,306],{"class":225},[182,1498,1499],{"class":229}," []                 ",[182,1501,1502],{"class":323},"# keep only the chart being exported\n",[182,1504,1505,1508,1510,1513],{"class":74,"line":327},[182,1506,1507],{"class":229},"        chart.width, chart.height ",[182,1509,306],{"class":225},[182,1511,1512],{"class":229}," size_cm         ",[182,1514,1515],{"class":323},"# reloaded charts use the default size\n",[182,1517,1518,1521,1523,1525,1528,1530,1532],{"class":74,"line":338},[182,1519,1520],{"class":229},"        page ",[182,1522,306],{"class":225},[182,1524,741],{"class":229},[182,1526,1527],{"class":191},"\"Render\"",[182,1529,353],{"class":229},[182,1531,42],{"class":272},[182,1533,385],{"class":229},[182,1535,1536,1539,1541],{"class":74,"line":388},[182,1537,1538],{"class":229},"        page.add_chart(chart, ",[182,1540,830],{"class":191},[182,1542,385],{"class":229},[182,1544,1545,1548,1550],{"class":74,"line":419},[182,1546,1547],{"class":229},"        wb.active ",[182,1549,306],{"class":225},[182,1551,1552],{"class":272}," 0\n",[182,1554,1555,1558,1561,1564,1566,1569,1571,1574,1576,1578,1580,1583],{"class":74,"line":430},[182,1556,1557],{"class":229},"        wb.save(",[182,1559,1560],{"class":225},"f",[182,1562,1563],{"class":191},"\"",[182,1565,636],{"class":635},[182,1567,1568],{"class":229},"prefix",[182,1570,642],{"class":635},[182,1572,1573],{"class":191},"_",[182,1575,636],{"class":635},[182,1577,639],{"class":229},[182,1579,642],{"class":635},[182,1581,1582],{"class":191},".xlsx\"",[182,1584,385],{"class":229},[182,1586,1587,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616,1618,1620,1622,1624,1626],{"class":74,"line":449},[182,1588,1589],{"class":229},"        subprocess.run([",[182,1591,854],{"class":191},[182,1593,353],{"class":229},[182,1595,859],{"class":191},[182,1597,353],{"class":229},[182,1599,864],{"class":191},[182,1601,353],{"class":229},[182,1603,869],{"class":191},[182,1605,353],{"class":229},[182,1607,1560],{"class":225},[182,1609,1563],{"class":191},[182,1611,636],{"class":635},[182,1613,1568],{"class":229},[182,1615,642],{"class":635},[182,1617,1573],{"class":191},[182,1619,636],{"class":635},[182,1621,639],{"class":229},[182,1623,642],{"class":635},[182,1625,1582],{"class":191},[182,1627,876],{"class":229},[182,1629,1631,1634,1636,1638,1640,1642,1644,1646,1648,1650,1652,1654],{"class":74,"line":1630},16,[182,1632,1633],{"class":344},"                       check",[182,1635,306],{"class":225},[182,1637,382],{"class":272},[182,1639,353],{"class":229},[182,1641,890],{"class":344},[182,1643,306],{"class":225},[182,1645,382],{"class":272},[182,1647,353],{"class":229},[182,1649,899],{"class":344},[182,1651,306],{"class":225},[182,1653,904],{"class":272},[182,1655,385],{"class":229},[182,1657,1659,1662,1664,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684,1686,1688,1690,1692,1694,1696,1698,1700,1702,1705],{"class":74,"line":1658},17,[182,1660,1661],{"class":229},"        sizes.append(crop_white(",[182,1663,1560],{"class":225},[182,1665,1563],{"class":191},[182,1667,636],{"class":635},[182,1669,1568],{"class":229},[182,1671,642],{"class":635},[182,1673,1573],{"class":191},[182,1675,636],{"class":635},[182,1677,639],{"class":229},[182,1679,642],{"class":635},[182,1681,645],{"class":191},[182,1683,353],{"class":229},[182,1685,1560],{"class":225},[182,1687,1563],{"class":191},[182,1689,636],{"class":635},[182,1691,1568],{"class":229},[182,1693,642],{"class":635},[182,1695,1573],{"class":191},[182,1697,636],{"class":635},[182,1699,639],{"class":229},[182,1701,642],{"class":635},[182,1703,1704],{"class":191},"_cropped.png\"",[182,1706,416],{"class":229},[182,1708,1710,1712],{"class":74,"line":1709},18,[182,1711,452],{"class":225},[182,1713,1714],{"class":229}," sizes\n",[182,1716,1718],{"class":74,"line":1717},19,[182,1719,237],{"emptyLinePlaceholder":236},[182,1721,1723,1725,1728,1730,1732,1735,1738],{"class":74,"line":1722},20,[182,1724,1044],{"class":272},[182,1726,1727],{"class":229},"(export_charts(",[182,1729,840],{"class":191},[182,1731,353],{"class":229},[182,1733,1734],{"class":191},"\"ChartOnly\"",[182,1736,1737],{"class":229},"))     ",[182,1739,1740],{"class":323},"# [(614, 349)]\n",[10,1742,1743],{},"Loading a fresh copy for each chart keeps the exports independent. A reloaded chart comes back at openpyxl's default size rather than the size it had in the workbook, so the function sets the size explicitly — without that line the same chart rendered noticeably smaller in our test. Tried on a workbook with three styled charts — stacked columns with labels, a 100% stack and horizontal bars — each came out as its own image with colours, labels and legends intact. openpyxl models most chart features, but anything it does not understand is dropped when it reloads a chart, so compare the exports with the workbook once when adopting this for a new report.",[167,1745,1747],{"id":1746},"route-3-redraw-it-with-matplotlib","Route 3 — Redraw it with matplotlib",[10,1749,1750],{},"When every image must look identical regardless of which office suite is installed, or when you produce hundreds of charts, skip the workbook renderer and draw the same chart from the same data:",[172,1752,1754],{"className":216,"code":1753,"language":218,"meta":177,"style":177},"import matplotlib\nmatplotlib.use(\"Agg\")\nimport matplotlib.pyplot as plt\n\nmonths, revenue = [\"Jan\", \"Feb\", \"Mar\", \"Apr\"], [82, 91, 78, 104]\nfig, ax = plt.subplots(figsize=(6.4, 3.6), dpi=200)\nax.bar(months, revenue, color=\"#4338CA\")\nax.set_title(\"Revenue\")\nax.spines[[\"top\", \"right\"]].set_visible(False)\nfig.tight_layout()\nfig.savefig(\"chart_mpl.png\")\nplt.close(fig)\nprint(Image.open(\"chart_mpl.png\").size)            # (1280, 720)\n",[179,1755,1756,1763,1773,1785,1789,1833,1869,1884,1893,1913,1918,1928,1933],{"__ignoreMap":177},[182,1757,1758,1760],{"class":74,"line":184},[182,1759,226],{"class":225},[182,1761,1762],{"class":229}," matplotlib\n",[182,1764,1765,1768,1771],{"class":74,"line":233},[182,1766,1767],{"class":229},"matplotlib.use(",[182,1769,1770],{"class":191},"\"Agg\"",[182,1772,385],{"class":229},[182,1774,1775,1777,1780,1782],{"class":74,"line":240},[182,1776,226],{"class":225},[182,1778,1779],{"class":229}," matplotlib.pyplot ",[182,1781,480],{"class":225},[182,1783,1784],{"class":229}," plt\n",[182,1786,1787],{"class":74,"line":254},[182,1788,237],{"emptyLinePlaceholder":236},[182,1790,1791,1794,1796,1799,1801,1803,1805,1807,1809,1811,1813,1816,1818,1820,1822,1824,1826,1828,1830],{"class":74,"line":267},[182,1792,1793],{"class":229},"months, revenue ",[182,1795,306],{"class":225},[182,1797,1798],{"class":229}," [",[182,1800,778],{"class":191},[182,1802,353],{"class":229},[182,1804,789],{"class":191},[182,1806,353],{"class":229},[182,1808,799],{"class":191},[182,1810,353],{"class":229},[182,1812,809],{"class":191},[182,1814,1815],{"class":229},"], [",[182,1817,783],{"class":272},[182,1819,353],{"class":229},[182,1821,794],{"class":272},[182,1823,353],{"class":229},[182,1825,804],{"class":272},[182,1827,353],{"class":229},[182,1829,814],{"class":272},[182,1831,1832],{"class":229},"]\n",[182,1834,1835,1838,1840,1843,1846,1848,1850,1853,1855,1858,1860,1863,1865,1867],{"class":74,"line":282},[182,1836,1837],{"class":229},"fig, ax ",[182,1839,306],{"class":225},[182,1841,1842],{"class":229}," plt.subplots(",[182,1844,1845],{"class":344},"figsize",[182,1847,306],{"class":225},[182,1849,597],{"class":229},[182,1851,1852],{"class":272},"6.4",[182,1854,353],{"class":229},[182,1856,1857],{"class":272},"3.6",[182,1859,374],{"class":229},[182,1861,1862],{"class":344},"dpi",[182,1864,306],{"class":225},[182,1866,1149],{"class":272},[182,1868,385],{"class":229},[182,1870,1871,1874,1877,1879,1882],{"class":74,"line":287},[182,1872,1873],{"class":229},"ax.bar(months, revenue, ",[182,1875,1876],{"class":344},"color",[182,1878,306],{"class":225},[182,1880,1881],{"class":191},"\"#4338CA\"",[182,1883,385],{"class":229},[182,1885,1886,1889,1891],{"class":74,"line":300},[182,1887,1888],{"class":229},"ax.set_title(",[182,1890,759],{"class":191},[182,1892,385],{"class":229},[182,1894,1895,1898,1901,1903,1906,1909,1911],{"class":74,"line":312},[182,1896,1897],{"class":229},"ax.spines[[",[182,1899,1900],{"class":191},"\"top\"",[182,1902,353],{"class":229},[182,1904,1905],{"class":191},"\"right\"",[182,1907,1908],{"class":229},"]].set_visible(",[182,1910,660],{"class":272},[182,1912,385],{"class":229},[182,1914,1915],{"class":74,"line":327},[182,1916,1917],{"class":229},"fig.tight_layout()\n",[182,1919,1920,1923,1926],{"class":74,"line":338},[182,1921,1922],{"class":229},"fig.savefig(",[182,1924,1925],{"class":191},"\"chart_mpl.png\"",[182,1927,385],{"class":229},[182,1929,1930],{"class":74,"line":388},[182,1931,1932],{"class":229},"plt.close(fig)\n",[182,1934,1935,1937,1940,1942,1945],{"class":74,"line":419},[182,1936,1044],{"class":272},[182,1938,1939],{"class":229},"(Image.open(",[182,1941,1925],{"class":191},[182,1943,1944],{"class":229},").size)            ",[182,1946,1947],{"class":323},"# (1280, 720)\n",[10,1949,1950,1951,551],{},"The image is fast, reproducible and exactly as styled — but it is a second chart definition that must be kept in step with the one in the workbook. Put the data and styling in one function used for both when the two must match. Going the other way — putting a matplotlib chart into a workbook — is covered in ",[14,1952,1954],{"href":1953},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002F","Embed a Matplotlib Chart in an Excel Report",[20,1956,29,1961,29,1964,29,1967,29,1970,29,1975,29,1982,29,1988,29,1994,29,2000,29,2006,29,2010,29,2015,29,2019,29,2026,29,2029,29,2035,29,2038,29,2044,29,2049,29,2053,29,2057,29,2062,29,2065,29,2070,29,2073,29,2078,29,2081],{"viewBox":1957,"role":23,"ariaLabelledBy":1958,"xmlns":27,"style":28},"4 0 752 233",[1959,1960],"ce-routes-t","ce-routes-d",[31,1962,1963],{"id":1959},"Rendering the workbook chart or redrawing it",[35,1965,1966],{"id":1960},"Rendering the workbook's own chart with Excel or LibreOffice guarantees the image matches the workbook, but needs an office program and depends on its rendering. Redrawing with matplotlib needs only Python and gives full control and speed, but duplicates the chart definition.",[39,1968],{"x":41,"y":42,"width":43,"height":1969,"fill":45},"233",[39,1971],{"x":106,"y":49,"width":1972,"height":1973,"rx":1974,"fill":133,"stroke":134,"style":55},"351.0","201.0","14",[57,1976,1978],{"transform":1977},"translate(182.5,29.0) scale(1.083)",[61,1979],{"d":1980,"fill":64,"stroke":1119,"style":1981},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[68,1983,1987],{"x":1984,"y":1985,"style":1986},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Render the workbook chart",[74,1989],{"x1":1990,"y1":1991,"x2":1992,"y2":1991,"stroke":134,"style":1993},"36.0","91.0","355.0","stroke-width:1px",[68,1995,1999],{"x":1996,"y":1997,"style":1998},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","matches the workbook",[57,2001,2003],{"transform":2002},"translate(38.5,99.5) scale(0.625)",[61,2004],{"d":1195,"fill":64,"stroke":134,"style":2005},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[68,2007,2009],{"x":1996,"y":2008,"style":1998},"137.0","one chart definition",[57,2011,2013],{"transform":2012},"translate(38.5,125.5) scale(0.625)",[61,2014],{"d":1195,"fill":64,"stroke":134,"style":2005},[68,2016,2018],{"x":1996,"y":2017,"style":1998},"163.0","needs Excel or LibreOffice",[57,2020,2022],{"transform":2021},"translate(38.5,151.5) scale(0.625)",[61,2023],{"d":2024,"fill":64,"stroke":2025,"style":2005},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[68,2027,2028],{"x":1996,"y":111,"style":1998},"renderer's fonts and styling",[57,2030,2032],{"transform":2031},"translate(38.5,177.5) scale(0.625)",[61,2033],{"d":2034,"fill":64,"stroke":158,"style":2005},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[39,2036],{"x":2037,"y":49,"width":1972,"height":1973,"rx":1974,"fill":157,"stroke":158,"style":55},"389.0",[57,2039,2041],{"transform":2040},"translate(551.5,29.0) scale(1.083)",[61,2042],{"d":2043,"fill":64,"stroke":1169,"style":1981},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01",[68,2045,2048],{"x":2046,"y":1985,"style":2047},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Redraw with matplotlib",[74,2050],{"x1":2051,"y1":1991,"x2":2052,"y2":1991,"stroke":158,"style":1993},"405.0","724.0",[68,2054,2056],{"x":2055,"y":1997,"style":1998},"429.0","only Python needed",[57,2058,2060],{"transform":2059},"translate(407.5,99.5) scale(0.625)",[61,2061],{"d":1195,"fill":64,"stroke":134,"style":2005},[68,2063,2064],{"x":2055,"y":2008,"style":1998},"fast at scale, any resolution",[57,2066,2068],{"transform":2067},"translate(407.5,125.5) scale(0.625)",[61,2069],{"d":1195,"fill":64,"stroke":134,"style":2005},[68,2071,2072],{"x":2055,"y":2017,"style":1998},"second definition to maintain",[57,2074,2076],{"transform":2075},"translate(407.5,151.5) scale(0.625)",[61,2077],{"d":2024,"fill":64,"stroke":2025,"style":2005},[68,2079,2080],{"x":2055,"y":111,"style":1998},"identical on every machine",[57,2082,2084],{"transform":2083},"translate(407.5,177.5) scale(0.625)",[61,2085],{"d":1195,"fill":64,"stroke":134,"style":2005},[167,2087,2089],{"id":2088},"common-pitfalls","Common pitfalls",[2091,2092,2093,2101,2107,2120,2126],"ul",{},[2094,2095,2096,2100],"li",{},[2097,2098,2099],"strong",{},"Charts on chartsheets."," They rendered as blank pages through LibreOffice. Use a worksheet with the chart at A1.",[2094,2102,2103,2106],{},[2097,2104,2105],{},"Several charts on one sheet."," The page render includes all of them. Build one chart-only workbook per image, or crop by position.",[2094,2108,2109,2112,2113,2116,2117,551],{},[2097,2110,2111],{},"Rendering in parallel."," Two ",[179,2114,2115],{},"soffice --headless"," processes sharing one user profile can block each other. Render files one after another, or give each worker its own profile with ",[179,2118,2119],{},"-env:UserInstallation=file:\u002F\u002F\u002Ftmp\u002Flo_profile_N",[2094,2121,2122,2125],{},[2097,2123,2124],{},"Leftover render files."," Every export leaves a helper workbook and a full-page PNG behind. Write them to a temporary directory and delete it when the images are done.",[2094,2127,2128,2131],{},[2097,2129,2130],{},"Fonts on servers."," LibreOffice substitutes fonts that are not installed, which changes text widths. Install the report's fonts on the render machine.",[167,2133,2135],{"id":2134},"conclusion","Conclusion",[10,2137,2138,2139,2141,2142,2144],{},"openpyxl only describes charts, so an image needs a renderer. On Windows with Excel, ",[179,2140,112],{}," through COM gives an exact picture. On a server, put the chart alone at A1 of a workbook's first sheet, convert it with headless LibreOffice — to PNG for speed or to PDF plus ",[179,2143,208],{}," for resolution — and crop the page margins with Pillow. When images must be identical everywhere or produced at scale, redraw the chart from the same data with matplotlib.",[167,2146,2148],{"id":2147},"frequently-asked-questions","Frequently asked questions",[10,2150,2151,2154],{},[2097,2152,2153],{},"Can openpyxl save a chart as an image?","\nNo. openpyxl writes the chart's definition into the workbook but never draws it. A program that renders charts — Excel, LibreOffice — has to produce the picture, or you redraw the chart from the data with a plotting library.",[10,2156,2157,2160],{},[2097,2158,2159],{},"How do I export a chart to PNG with Excel installed?","\nOpen the workbook with win32com and call Export on the chart, for example ws.ChartObjects(1).Chart.Export with the full path of the PNG to write. Excel renders it exactly as it appears in the workbook.",[10,2162,2163,2166],{},[2097,2164,2165],{},"How do I export a chart on a Linux server?","\nPut the chart alone on the first sheet of a workbook, convert that workbook with headless LibreOffice to PNG or PDF, and crop the white page margins with Pillow. The PDF route with pdftoppm gives a higher resolution.",[10,2168,2169,2172],{},[2097,2170,2171],{},"Why is the exported image blank?","\nA chart on a chartsheet rendered as an empty page in LibreOffice in our test. Place the chart on an ordinary worksheet at A1 instead, with its data on another sheet.",[167,2174,2176],{"id":2175},"related","Related",[2091,2178,2179,2185,2192,2199],{},[2094,2180,2181,2182,2184],{},"Up one level: ",[14,2183,17],{"href":16}," — pictures in reports.",[2094,2186,2187,2191],{},[14,2188,2190],{"href":2189},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Finsert-an-image-from-memory-into-excel\u002F","Insert an Image from Memory into Excel with Python"," — putting images back into workbooks.",[2094,2193,2194,2198],{},[14,2195,2197],{"href":2196},"\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"," — the PDF route for whole sheets.",[2094,2200,2201,2205],{},[14,2202,2204],{"href":2203},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl\u002F","Create a Bar Chart in Excel with openpyxl"," — building the chart first.",[2207,2208,2209],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":177,"searchDepth":233,"depth":233,"links":2211},[2212,2213,2214,2215,2216,2217,2218,2219,2220,2221],{"id":169,"depth":233,"text":170},{"id":458,"depth":233,"text":459},{"id":692,"depth":233,"text":693},{"id":1207,"depth":233,"text":1208},{"id":1373,"depth":233,"text":1374},{"id":1746,"depth":233,"text":1747},{"id":2088,"depth":233,"text":2089},{"id":2134,"depth":233,"text":2135},{"id":2147,"depth":233,"text":2148},{"id":2175,"depth":233,"text":2176},"2026-09-23","Turn a chart inside an Excel workbook into a PNG for an email, a slide or a web page — with Excel itself through COM on Windows, with headless LibreOffice on any server plus a Pillow crop, or by redrawing the chart from the same data with matplotlib — and choose by fidelity, platform and resolution.","md",[2226,2228,2230,2232],{"q":2153,"a":2227},"No. openpyxl writes the chart's definition into the workbook but never draws it. A program that renders charts — Excel, LibreOffice — has to produce the picture, or you redraw the chart from the data with a plotting library.",{"q":2159,"a":2229},"Open the workbook with win32com and call Export on the chart, for example ws.ChartObjects(1).Chart.Export with the full path of the PNG to write. Excel renders it exactly as it appears in the workbook.",{"q":2165,"a":2231},"Put the chart alone on the first sheet of a workbook, convert that workbook with headless LibreOffice to PNG or PDF, and crop the white page margins with Pillow. The PDF route with pdftoppm gives a higher resolution.",{"q":2171,"a":2233},"A chart on a chartsheet rendered as an empty page in LibreOffice in our test. Place the chart on an ordinary worksheet at A1 instead, with its data on another sheet.","export an excel chart as an image with python",null,{"breadcrumb":2237},[2238,2241,2244],{"name":2239,"item":2240},"Home","\u002F",{"name":2242,"item":2243},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":17,"item":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image",{"title":2247,"description":2248},"Export an Excel Chart as an Image (PNG) with Python","Save Excel charts as PNG images from Python — Chart.Export through win32com, headless LibreOffice to PNG or PDF with pdftoppm and a Pillow crop, isolating one chart per sheet, resolution, and when to redraw with matplotlib instead.","export-an-excel-chart-as-an-image","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fexport-an-excel-chart-as-an-image\u002Findex",[2252,2253],"charts-and-images","com-automation","how-to","dZ3uYxT6n_A-gfAfrogf-62qsJeW4f3Siax0MbFoa6w",[2257,2261],{"title":2258,"path":2259,"stem":2260,"children":-1},"Embed Images Inside Cells with xlsxwriter","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-images-inside-cells-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-images-inside-cells-with-xlsxwriter\u002Findex",{"title":2262,"path":2263,"stem":2264,"children":-1},"Extract Images from an Excel File with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fextract-images-from-an-excel-file-with-python","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fextract-images-from-an-excel-file-with-python\u002Findex",[2266,2268],{"path":2267,"title":2242},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2269,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel",[],[2272,2275,2278,2281,2284,2287],{"path":2273,"title":2274,"kind":2254},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","Build a Dashboard Sheet with Charts from Multiple Tabs",{"path":2276,"title":2277,"kind":2254},"\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":2279,"title":2280,"kind":2254},"\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":2282,"title":2283,"kind":2254},"\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":2285,"title":2286,"kind":2254},"\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":2288,"title":2289,"kind":2254},"\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",1790972116713]