[{"data":1,"prerenderedAt":1984},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl":1975},{"id":4,"title":5,"body":6,"dateModified":1952,"datePublished":1952,"description":1953,"extension":1954,"faq":1955,"meta":1966,"navigation":233,"path":1967,"seo":1968,"slug":1971,"stem":1972,"type":1973,"__hash__":1974},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl\u002Findex.md","Create a Scatter Chart in Excel with openpyxl",{"type":7,"value":8,"toc":1936},"minimark",[9,19,149,154,179,183,186,575,586,590,593,658,680,684,687,750,757,829,833,836,895,927,931,1167,1173,1177,1180,1448,1455,1459,1462,1614,1625,1629,1632,1694,1697,1742,1746,1804,1808,1815,1819,1833,1837,1847,1859,1865,1875,1892,1896,1932],[10,11,12,13,18],"p",{},"A scatter chart is the right choice whenever both variables are numeric — price against volume, headcount against cost, forecast against actual — because it places points by value rather than treating one axis as evenly spaced categories. openpyxl builds one from a worksheet's own cells, so the chart stays live: edit the numbers and it redraws. This guide plots a single series, styles it as markers, adds axis titles and a trendline, then extends to several series on one chart. It belongs to ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",".",[20,21,29,30,29,34,29,38,29,45,29,52,29,57,29,65,29,73,29,80,29,83,29,89,29,93,29,97,29,101,29,105,29,110,29,114,29,119,29,121,29,128,29,133,29,136,29,139,29,142,29,145],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"0 0 760 244","img",[25,26],"sc-t","sc-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},"Scatter versus line: what each axis means",[35,36,37],"desc",{"id":26},"A scatter chart positions each point by its numeric x value so uneven gaps are visible, while a line chart spaces categories evenly and hides the gaps.",[39,40],"rect",{"x":41,"y":41,"width":42,"height":43,"fill":44},"0","760","244","#ffffff",[46,47,51],"text",{"x":48,"y":49,"style":50},"196","28","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","scatter: x is a number",[46,53,56],{"x":54,"y":49,"style":55},"566","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","line: x is a label",[58,59],"line",{"x1":60,"y1":61,"x2":60,"y2":62,"stroke":63,"style":64},"380","40","226","var(--line,#cdd5e6)","stroke-width:1px",[39,66],{"x":67,"y":68,"width":69,"height":70,"rx":71,"fill":72,"stroke":63},"34","46","322","150","10","#d9f4f1",[58,74],{"x1":75,"y1":76,"x2":77,"y2":76,"stroke":78,"style":79},"64","176","330","var(--teal-ink,#0b6157)","stroke-width:1.5px",[58,81],{"x1":75,"y1":82,"x2":75,"y2":76,"stroke":78,"style":79},"70",[84,85],"circle",{"cx":86,"cy":70,"r":87,"fill":88},"90","5","#0f9488",[84,90],{"cx":91,"cy":92,"r":87,"fill":88},"112","138",[84,94],{"cx":95,"cy":96,"r":87,"fill":88},"140","120",[84,98],{"cx":99,"cy":100,"r":87,"fill":88},"255","96",[84,102],{"cx":103,"cy":104,"r":87,"fill":88},"300","80",[46,106,109],{"x":48,"y":107,"style":108},"212","font-size:11.5px;fill:var(--teal-ink,#0b6157);text-anchor:middle","the gap between 140 and 255 is real",[39,111],{"x":112,"y":68,"width":69,"height":70,"rx":71,"fill":113,"stroke":63},"404","#fee8f2",[58,115],{"x1":116,"y1":76,"x2":117,"y2":76,"stroke":118,"style":79},"434","700","var(--accent-ink,#be185d)",[58,120],{"x1":116,"y1":82,"x2":116,"y2":76,"stroke":118,"style":79},[122,123],"polyline",{"points":124,"fill":125,"stroke":126,"style":127},"466,150 519,138 572,120 625,96 678,80","none","var(--accent,#f43f8f)","stroke-width:2px",[84,129],{"cx":130,"cy":70,"r":131,"fill":132},"466","4","#be185d",[84,134],{"cx":135,"cy":92,"r":131,"fill":132},"519",[84,137],{"cx":138,"cy":96,"r":131,"fill":132},"572",[84,140],{"cx":141,"cy":100,"r":131,"fill":132},"625",[84,143],{"cx":144,"cy":104,"r":131,"fill":132},"678",[46,146,148],{"x":54,"y":107,"style":147},"font-size:11.5px;fill:var(--accent-ink,#be185d);text-anchor:middle","evenly spaced — the gap disappears",[150,151,153],"h2",{"id":152},"prerequisites","Prerequisites",[155,156,161],"pre",{"className":157,"code":158,"language":159,"meta":160,"style":160},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[162,163,164],"code",{"__ignoreMap":160},[165,166,168,172,176],"span",{"class":58,"line":167},1,[165,169,171],{"class":170},"sMTad","pip",[165,173,175],{"class":174},"srMev"," install",[165,177,178],{"class":174}," openpyxl\n",[150,180,182],{"id":181},"write-data-and-add-a-scatter-chart","Write data and add a scatter chart",[10,184,185],{},"A chart references cells, so the data has to be on the sheet first:",[155,187,191],{"className":188,"code":189,"language":190,"meta":160,"style":160},"language-python shiki shiki-themes github-light github-dark-high-contrast","\"\"\"Plot marketing spend against revenue.\"\"\"\nfrom openpyxl import Workbook\nfrom openpyxl.chart import Reference, ScatterChart, Series\n\nwb = Workbook()\nws = wb.active\nws.title = \"Campaigns\"\n\nws.append([\"spend\", \"revenue\"])\nfor spend, revenue in [(1200, 8400), (1800, 11200), (2400, 13100),\n                       (5200, 24800), (6100, 26400), (9800, 31200)]:\n    ws.append([spend, revenue])\n\nchart = ScatterChart()\nchart.title = \"Revenue against marketing spend\"\nchart.style = 13\nchart.x_axis.title = \"Spend (GBP)\"\nchart.y_axis.title = \"Revenue (GBP)\"\n\nx_values = Reference(ws, min_col=1, min_row=2, max_row=ws.max_row)\ny_values = Reference(ws, min_col=2, min_row=1, max_row=ws.max_row)\nseries = Series(y_values, x_values, title_from_data=True)\nchart.series.append(series)\n\nws.add_chart(chart, \"D2\")\nwb.save(\"scatter.xlsx\")\n","python",[162,192,193,198,215,228,235,247,258,269,274,292,340,377,383,388,399,410,421,432,443,448,488,520,542,548,553,564],{"__ignoreMap":160},[165,194,195],{"class":58,"line":167},[165,196,197],{"class":174},"\"\"\"Plot marketing spend against revenue.\"\"\"\n",[165,199,201,205,209,212],{"class":58,"line":200},2,[165,202,204],{"class":203},"s-kum","from",[165,206,208],{"class":207},"skGVy"," openpyxl ",[165,210,211],{"class":203},"import",[165,213,214],{"class":207}," Workbook\n",[165,216,218,220,223,225],{"class":58,"line":217},3,[165,219,204],{"class":203},[165,221,222],{"class":207}," openpyxl.chart ",[165,224,211],{"class":203},[165,226,227],{"class":207}," Reference, ScatterChart, Series\n",[165,229,231],{"class":58,"line":230},4,[165,232,234],{"emptyLinePlaceholder":233},true,"\n",[165,236,238,241,244],{"class":58,"line":237},5,[165,239,240],{"class":207},"wb ",[165,242,243],{"class":203},"=",[165,245,246],{"class":207}," Workbook()\n",[165,248,250,253,255],{"class":58,"line":249},6,[165,251,252],{"class":207},"ws ",[165,254,243],{"class":203},[165,256,257],{"class":207}," wb.active\n",[165,259,261,264,266],{"class":58,"line":260},7,[165,262,263],{"class":207},"ws.title ",[165,265,243],{"class":203},[165,267,268],{"class":174}," \"Campaigns\"\n",[165,270,272],{"class":58,"line":271},8,[165,273,234],{"emptyLinePlaceholder":233},[165,275,277,280,283,286,289],{"class":58,"line":276},9,[165,278,279],{"class":207},"ws.append([",[165,281,282],{"class":174},"\"spend\"",[165,284,285],{"class":207},", ",[165,287,288],{"class":174},"\"revenue\"",[165,290,291],{"class":207},"])\n",[165,293,295,298,301,304,307,311,313,316,319,322,324,327,329,332,334,337],{"class":58,"line":294},10,[165,296,297],{"class":203},"for",[165,299,300],{"class":207}," spend, revenue ",[165,302,303],{"class":203},"in",[165,305,306],{"class":207}," [(",[165,308,310],{"class":309},"sP0c6","1200",[165,312,285],{"class":207},[165,314,315],{"class":309},"8400",[165,317,318],{"class":207},"), (",[165,320,321],{"class":309},"1800",[165,323,285],{"class":207},[165,325,326],{"class":309},"11200",[165,328,318],{"class":207},[165,330,331],{"class":309},"2400",[165,333,285],{"class":207},[165,335,336],{"class":309},"13100",[165,338,339],{"class":207},"),\n",[165,341,343,346,349,351,354,356,359,361,364,366,369,371,374],{"class":58,"line":342},11,[165,344,345],{"class":207},"                       (",[165,347,348],{"class":309},"5200",[165,350,285],{"class":207},[165,352,353],{"class":309},"24800",[165,355,318],{"class":207},[165,357,358],{"class":309},"6100",[165,360,285],{"class":207},[165,362,363],{"class":309},"26400",[165,365,318],{"class":207},[165,367,368],{"class":309},"9800",[165,370,285],{"class":207},[165,372,373],{"class":309},"31200",[165,375,376],{"class":207},")]:\n",[165,378,380],{"class":58,"line":379},12,[165,381,382],{"class":207},"    ws.append([spend, revenue])\n",[165,384,386],{"class":58,"line":385},13,[165,387,234],{"emptyLinePlaceholder":233},[165,389,391,394,396],{"class":58,"line":390},14,[165,392,393],{"class":207},"chart ",[165,395,243],{"class":203},[165,397,398],{"class":207}," ScatterChart()\n",[165,400,402,405,407],{"class":58,"line":401},15,[165,403,404],{"class":207},"chart.title ",[165,406,243],{"class":203},[165,408,409],{"class":174}," \"Revenue against marketing spend\"\n",[165,411,413,416,418],{"class":58,"line":412},16,[165,414,415],{"class":207},"chart.style ",[165,417,243],{"class":203},[165,419,420],{"class":309}," 13\n",[165,422,424,427,429],{"class":58,"line":423},17,[165,425,426],{"class":207},"chart.x_axis.title ",[165,428,243],{"class":203},[165,430,431],{"class":174}," \"Spend (GBP)\"\n",[165,433,435,438,440],{"class":58,"line":434},18,[165,436,437],{"class":207},"chart.y_axis.title ",[165,439,243],{"class":203},[165,441,442],{"class":174}," \"Revenue (GBP)\"\n",[165,444,446],{"class":58,"line":445},19,[165,447,234],{"emptyLinePlaceholder":233},[165,449,451,454,456,459,463,465,468,470,473,475,478,480,483,485],{"class":58,"line":450},20,[165,452,453],{"class":207},"x_values ",[165,455,243],{"class":203},[165,457,458],{"class":207}," Reference(ws, ",[165,460,462],{"class":461},"sa561","min_col",[165,464,243],{"class":203},[165,466,467],{"class":309},"1",[165,469,285],{"class":207},[165,471,472],{"class":461},"min_row",[165,474,243],{"class":203},[165,476,477],{"class":309},"2",[165,479,285],{"class":207},[165,481,482],{"class":461},"max_row",[165,484,243],{"class":203},[165,486,487],{"class":207},"ws.max_row)\n",[165,489,491,494,496,498,500,502,504,506,508,510,512,514,516,518],{"class":58,"line":490},21,[165,492,493],{"class":207},"y_values ",[165,495,243],{"class":203},[165,497,458],{"class":207},[165,499,462],{"class":461},[165,501,243],{"class":203},[165,503,477],{"class":309},[165,505,285],{"class":207},[165,507,472],{"class":461},[165,509,243],{"class":203},[165,511,467],{"class":309},[165,513,285],{"class":207},[165,515,482],{"class":461},[165,517,243],{"class":203},[165,519,487],{"class":207},[165,521,523,526,528,531,534,536,539],{"class":58,"line":522},22,[165,524,525],{"class":207},"series ",[165,527,243],{"class":203},[165,529,530],{"class":207}," Series(y_values, x_values, ",[165,532,533],{"class":461},"title_from_data",[165,535,243],{"class":203},[165,537,538],{"class":309},"True",[165,540,541],{"class":207},")\n",[165,543,545],{"class":58,"line":544},23,[165,546,547],{"class":207},"chart.series.append(series)\n",[165,549,551],{"class":58,"line":550},24,[165,552,234],{"emptyLinePlaceholder":233},[165,554,556,559,562],{"class":58,"line":555},25,[165,557,558],{"class":207},"ws.add_chart(chart, ",[165,560,561],{"class":174},"\"D2\"",[165,563,541],{"class":207},[165,565,567,570,573],{"class":58,"line":566},26,[165,568,569],{"class":207},"wb.save(",[165,571,572],{"class":174},"\"scatter.xlsx\"",[165,574,541],{"class":207},[10,576,577,578,581,582,585],{},"Note the asymmetry in the two ",[162,579,580],{},"Reference"," calls, which is the single most common source of an off-by-one chart. The y-values range starts at row 1 because ",[162,583,584],{},"title_from_data=True"," consumes that first cell as the series name; the x-values range starts at row 2 because it is pure data.",[150,587,589],{"id":588},"show-markers-not-lines","Show markers, not lines",[10,591,592],{},"By default openpyxl joins the points, which turns a scatter chart back into a line chart. Two lines fix it:",[155,594,596],{"className":188,"code":595,"language":190,"meta":160,"style":160},"from openpyxl.chart.marker import Marker\n\nseries.marker = Marker(symbol=\"circle\", size=7)\nseries.graphicalProperties.line.noFill = True      # points only\n",[162,597,598,610,614,644],{"__ignoreMap":160},[165,599,600,602,605,607],{"class":58,"line":167},[165,601,204],{"class":203},[165,603,604],{"class":207}," openpyxl.chart.marker ",[165,606,211],{"class":203},[165,608,609],{"class":207}," Marker\n",[165,611,612],{"class":58,"line":200},[165,613,234],{"emptyLinePlaceholder":233},[165,615,616,619,621,624,627,629,632,634,637,639,642],{"class":58,"line":217},[165,617,618],{"class":207},"series.marker ",[165,620,243],{"class":203},[165,622,623],{"class":207}," Marker(",[165,625,626],{"class":461},"symbol",[165,628,243],{"class":203},[165,630,631],{"class":174},"\"circle\"",[165,633,285],{"class":207},[165,635,636],{"class":461},"size",[165,638,243],{"class":203},[165,640,641],{"class":309},"7",[165,643,541],{"class":207},[165,645,646,649,651,654],{"class":58,"line":230},[165,647,648],{"class":207},"series.graphicalProperties.line.noFill ",[165,650,243],{"class":203},[165,652,653],{"class":309}," True",[165,655,657],{"class":656},"s-wDw","      # points only\n",[10,659,660,661,285,663,285,666,285,669,285,672,675,676,679],{},"Available symbols include ",[162,662,84],{},[162,664,665],{},"square",[162,667,668],{},"diamond",[162,670,671],{},"triangle",[162,673,674],{},"x"," and ",[162,677,678],{},"star",". Use a different symbol per series when a chart carries more than one, so it stays readable in a monochrome printout.",[150,681,683],{"id":682},"title-the-axes-and-control-the-range","Title the axes and control the range",[10,685,686],{},"Unlabelled axes make a chart unusable to anyone who did not build it:",[155,688,690],{"className":188,"code":689,"language":190,"meta":160,"style":160},"chart.x_axis.title = \"Spend (GBP)\"\nchart.y_axis.title = \"Revenue (GBP)\"\nchart.x_axis.scaling.min = 0\nchart.y_axis.scaling.min = 0\nchart.height = 9          # centimetres\nchart.width = 16\n",[162,691,692,700,708,718,727,740],{"__ignoreMap":160},[165,693,694,696,698],{"class":58,"line":167},[165,695,426],{"class":207},[165,697,243],{"class":203},[165,699,431],{"class":174},[165,701,702,704,706],{"class":58,"line":200},[165,703,437],{"class":207},[165,705,243],{"class":203},[165,707,442],{"class":174},[165,709,710,713,715],{"class":58,"line":217},[165,711,712],{"class":207},"chart.x_axis.scaling.min ",[165,714,243],{"class":203},[165,716,717],{"class":309}," 0\n",[165,719,720,723,725],{"class":58,"line":230},[165,721,722],{"class":207},"chart.y_axis.scaling.min ",[165,724,243],{"class":203},[165,726,717],{"class":309},[165,728,729,732,734,737],{"class":58,"line":237},[165,730,731],{"class":207},"chart.height ",[165,733,243],{"class":203},[165,735,736],{"class":309}," 9",[165,738,739],{"class":656},"          # centimetres\n",[165,741,742,745,747],{"class":58,"line":249},[165,743,744],{"class":207},"chart.width ",[165,746,243],{"class":203},[165,748,749],{"class":309}," 16\n",[10,751,752,753,756],{},"Anchoring both axes at zero matters for honesty as much as clarity: an axis starting at 8,000 exaggerates differences that a full-range view shows as marginal. Set ",[162,754,755],{},"scaling.max"," too when successive months' reports should be visually comparable, otherwise Excel rescales each one and a flat trend looks dramatic.",[20,758,29,763,29,766,29,769,29,771,29,776,29,779,29,785,29,789,29,794,29,797,29,802,29,804,29,808,29,811,29,815,29,817,29,820,29,825],{"viewBox":759,"role":23,"ariaLabelledBy":760,"xmlns":27,"style":28},"0 0 760 226",[761,762],"sc2-t","sc2-d",[31,764,765],{"id":761},"How the Reference ranges map onto the sheet",[35,767,768],{"id":762},"The x-values reference covers the data rows only, while the y-values reference includes the header cell because title_from_data consumes it as the series name.",[39,770],{"x":41,"y":41,"width":42,"height":62,"fill":44},[46,772,775],{"x":60,"y":773,"style":774},"26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Two ranges, two different starting rows",[39,777],{"x":95,"y":68,"width":70,"height":67,"rx":87,"fill":778,"stroke":63},"#f0f2f5",[46,780,784],{"x":781,"y":782,"style":783},"215","68","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","A1: spend",[39,786],{"x":787,"y":68,"width":70,"height":67,"rx":87,"fill":788,"stroke":63},"290","#ebebfd",[46,790,793],{"x":791,"y":782,"style":792},"365","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","B1: revenue",[39,795],{"x":95,"y":796,"width":70,"height":67,"rx":87,"fill":72,"stroke":63},"84",[46,798,801],{"x":781,"y":799,"style":800},"106","font-size:12px;fill:var(--teal-ink,#0b6157);text-anchor:middle","A2: 1200",[39,803],{"x":787,"y":796,"width":70,"height":67,"rx":87,"fill":788,"stroke":63},[46,805,807],{"x":791,"y":799,"style":806},"font-size:12px;fill:var(--text,#172033);text-anchor:middle","B2: 8400",[39,809],{"x":95,"y":810,"width":70,"height":67,"rx":87,"fill":72,"stroke":63},"122",[46,812,814],{"x":781,"y":813,"style":800},"144","A3: 1800",[39,816],{"x":787,"y":810,"width":70,"height":67,"rx":87,"fill":788,"stroke":63},[46,818,819],{"x":791,"y":813,"style":806},"B3: 11200",[46,821,824],{"x":60,"y":822,"style":823},"182","font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","x values start at row 2; y values start at row 1, where the header becomes the series name",[46,826,828],{"x":60,"y":827,"style":823},"204","Start both at row 1 and the first point plots the header as zero",[150,830,832],{"id":831},"add-a-trendline","Add a trendline",[10,834,835],{},"Excel computes the trendline itself, so the chart stays correct if the data changes:",[155,837,839],{"className":188,"code":838,"language":190,"meta":160,"style":160},"from openpyxl.chart.trendline import Trendline\n\nseries.trendline = Trendline(trendlineType=\"linear\", dispEq=True, dispRSqr=True)\n",[162,840,841,853,857],{"__ignoreMap":160},[165,842,843,845,848,850],{"class":58,"line":167},[165,844,204],{"class":203},[165,846,847],{"class":207}," openpyxl.chart.trendline ",[165,849,211],{"class":203},[165,851,852],{"class":207}," Trendline\n",[165,854,855],{"class":58,"line":200},[165,856,234],{"emptyLinePlaceholder":233},[165,858,859,862,864,867,870,872,875,877,880,882,884,886,889,891,893],{"class":58,"line":217},[165,860,861],{"class":207},"series.trendline ",[165,863,243],{"class":203},[165,865,866],{"class":207}," Trendline(",[165,868,869],{"class":461},"trendlineType",[165,871,243],{"class":203},[165,873,874],{"class":174},"\"linear\"",[165,876,285],{"class":207},[165,878,879],{"class":461},"dispEq",[165,881,243],{"class":203},[165,883,538],{"class":309},[165,885,285],{"class":207},[165,887,888],{"class":461},"dispRSqr",[165,890,243],{"class":203},[165,892,538],{"class":309},[165,894,541],{"class":207},[10,896,897,899,900,285,903,906,907,910,911,285,914,285,917,675,920,906,923,926],{},[162,898,869],{}," accepts ",[162,901,902],{},"linear",[162,904,905],{},"poly"," (with ",[162,908,909],{},"order","), ",[162,912,913],{},"exp",[162,915,916],{},"log",[162,918,919],{},"power",[162,921,922],{},"movingAvg",[162,924,925],{},"period","). Displaying the equation and R² is worth doing when the chart is making an argument — a visible R² of 0.31 stops a reader over-reading a line that fits nothing.",[150,928,930],{"id":929},"plot-several-series-on-one-chart","Plot several series on one chart",[155,932,934],{"className":188,"code":933,"language":190,"meta":160,"style":160},"from openpyxl.chart import Reference, ScatterChart, Series\nfrom openpyxl.chart.marker import Marker\n\nchart = ScatterChart()\nchart.title = \"Spend against revenue by region\"\nchart.x_axis.title = \"Spend (GBP)\"\nchart.y_axis.title = \"Revenue (GBP)\"\n\nx_values = Reference(ws, min_col=1, min_row=2, max_row=ws.max_row)\nfor col, symbol in ((2, \"circle\"), (3, \"diamond\"), (4, \"triangle\")):\n    values = Reference(ws, min_col=col, min_row=1, max_row=ws.max_row)\n    series = Series(values, x_values, title_from_data=True)\n    series.marker = Marker(symbol=symbol, size=7)\n    series.graphicalProperties.line.noFill = True\n    chart.series.append(series)\n\nws.add_chart(chart, \"F2\")\n",[162,935,936,946,956,960,968,977,985,993,997,1027,1067,1097,1115,1139,1149,1154,1158],{"__ignoreMap":160},[165,937,938,940,942,944],{"class":58,"line":167},[165,939,204],{"class":203},[165,941,222],{"class":207},[165,943,211],{"class":203},[165,945,227],{"class":207},[165,947,948,950,952,954],{"class":58,"line":200},[165,949,204],{"class":203},[165,951,604],{"class":207},[165,953,211],{"class":203},[165,955,609],{"class":207},[165,957,958],{"class":58,"line":217},[165,959,234],{"emptyLinePlaceholder":233},[165,961,962,964,966],{"class":58,"line":230},[165,963,393],{"class":207},[165,965,243],{"class":203},[165,967,398],{"class":207},[165,969,970,972,974],{"class":58,"line":237},[165,971,404],{"class":207},[165,973,243],{"class":203},[165,975,976],{"class":174}," \"Spend against revenue by region\"\n",[165,978,979,981,983],{"class":58,"line":249},[165,980,426],{"class":207},[165,982,243],{"class":203},[165,984,431],{"class":174},[165,986,987,989,991],{"class":58,"line":260},[165,988,437],{"class":207},[165,990,243],{"class":203},[165,992,442],{"class":174},[165,994,995],{"class":58,"line":271},[165,996,234],{"emptyLinePlaceholder":233},[165,998,999,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025],{"class":58,"line":276},[165,1000,453],{"class":207},[165,1002,243],{"class":203},[165,1004,458],{"class":207},[165,1006,462],{"class":461},[165,1008,243],{"class":203},[165,1010,467],{"class":309},[165,1012,285],{"class":207},[165,1014,472],{"class":461},[165,1016,243],{"class":203},[165,1018,477],{"class":309},[165,1020,285],{"class":207},[165,1022,482],{"class":461},[165,1024,243],{"class":203},[165,1026,487],{"class":207},[165,1028,1029,1031,1034,1036,1039,1041,1043,1045,1047,1050,1052,1055,1057,1059,1061,1064],{"class":58,"line":294},[165,1030,297],{"class":203},[165,1032,1033],{"class":207}," col, symbol ",[165,1035,303],{"class":203},[165,1037,1038],{"class":207}," ((",[165,1040,477],{"class":309},[165,1042,285],{"class":207},[165,1044,631],{"class":174},[165,1046,318],{"class":207},[165,1048,1049],{"class":309},"3",[165,1051,285],{"class":207},[165,1053,1054],{"class":174},"\"diamond\"",[165,1056,318],{"class":207},[165,1058,131],{"class":309},[165,1060,285],{"class":207},[165,1062,1063],{"class":174},"\"triangle\"",[165,1065,1066],{"class":207},")):\n",[165,1068,1069,1072,1074,1076,1078,1080,1083,1085,1087,1089,1091,1093,1095],{"class":58,"line":342},[165,1070,1071],{"class":207},"    values ",[165,1073,243],{"class":203},[165,1075,458],{"class":207},[165,1077,462],{"class":461},[165,1079,243],{"class":203},[165,1081,1082],{"class":207},"col, ",[165,1084,472],{"class":461},[165,1086,243],{"class":203},[165,1088,467],{"class":309},[165,1090,285],{"class":207},[165,1092,482],{"class":461},[165,1094,243],{"class":203},[165,1096,487],{"class":207},[165,1098,1099,1102,1104,1107,1109,1111,1113],{"class":58,"line":379},[165,1100,1101],{"class":207},"    series ",[165,1103,243],{"class":203},[165,1105,1106],{"class":207}," Series(values, x_values, ",[165,1108,533],{"class":461},[165,1110,243],{"class":203},[165,1112,538],{"class":309},[165,1114,541],{"class":207},[165,1116,1117,1120,1122,1124,1126,1128,1131,1133,1135,1137],{"class":58,"line":385},[165,1118,1119],{"class":207},"    series.marker ",[165,1121,243],{"class":203},[165,1123,623],{"class":207},[165,1125,626],{"class":461},[165,1127,243],{"class":203},[165,1129,1130],{"class":207},"symbol, ",[165,1132,636],{"class":461},[165,1134,243],{"class":203},[165,1136,641],{"class":309},[165,1138,541],{"class":207},[165,1140,1141,1144,1146],{"class":58,"line":390},[165,1142,1143],{"class":207},"    series.graphicalProperties.line.noFill ",[165,1145,243],{"class":203},[165,1147,1148],{"class":309}," True\n",[165,1150,1151],{"class":58,"line":401},[165,1152,1153],{"class":207},"    chart.series.append(series)\n",[165,1155,1156],{"class":58,"line":412},[165,1157,234],{"emptyLinePlaceholder":233},[165,1159,1160,1162,1165],{"class":58,"line":423},[165,1161,558],{"class":207},[165,1163,1164],{"class":174},"\"F2\"",[165,1166,541],{"class":207},[10,1168,1169,1170,1172],{},"All three series share one x-axis column here. When each series has its own x values — different campaigns measured at different spends — build a separate ",[162,1171,580],{}," per series and pass it as the second argument.",[150,1174,1176],{"id":1175},"build-the-sheet-from-a-dataframe","Build the sheet from a DataFrame",[10,1178,1179],{},"In a real report the data comes from pandas, so write it first and chart the written range:",[155,1181,1183],{"className":188,"code":1182,"language":190,"meta":160,"style":160},"import pandas as pd\nfrom openpyxl import load_workbook\nfrom openpyxl.chart import Reference, ScatterChart, Series\n\ndf = pd.DataFrame({\"spend\": [1200, 1800, 2400, 5200], \"revenue\": [8400, 11200, 13100, 24800]})\ndf.to_excel(\"campaigns.xlsx\", index=False, sheet_name=\"Campaigns\")\n\nwb = load_workbook(\"campaigns.xlsx\")\nws = wb[\"Campaigns\"]\n\nchart = ScatterChart()\nchart.title = \"Revenue against spend\"\nx_values = Reference(ws, min_col=1, min_row=2, max_row=ws.max_row)\ny_values = Reference(ws, min_col=2, min_row=1, max_row=ws.max_row)\nchart.series.append(Series(y_values, x_values, title_from_data=True))\nws.add_chart(chart, \"D2\")\nwb.save(\"campaigns.xlsx\")\n",[162,1184,1185,1198,1209,1219,1223,1276,1306,1310,1323,1337,1341,1349,1358,1388,1418,1432,1440],{"__ignoreMap":160},[165,1186,1187,1189,1192,1195],{"class":58,"line":167},[165,1188,211],{"class":203},[165,1190,1191],{"class":207}," pandas ",[165,1193,1194],{"class":203},"as",[165,1196,1197],{"class":207}," pd\n",[165,1199,1200,1202,1204,1206],{"class":58,"line":200},[165,1201,204],{"class":203},[165,1203,208],{"class":207},[165,1205,211],{"class":203},[165,1207,1208],{"class":207}," load_workbook\n",[165,1210,1211,1213,1215,1217],{"class":58,"line":217},[165,1212,204],{"class":203},[165,1214,222],{"class":207},[165,1216,211],{"class":203},[165,1218,227],{"class":207},[165,1220,1221],{"class":58,"line":230},[165,1222,234],{"emptyLinePlaceholder":233},[165,1224,1225,1228,1230,1233,1235,1238,1240,1242,1244,1246,1248,1250,1252,1255,1257,1259,1261,1263,1265,1267,1269,1271,1273],{"class":58,"line":237},[165,1226,1227],{"class":207},"df ",[165,1229,243],{"class":203},[165,1231,1232],{"class":207}," pd.DataFrame({",[165,1234,282],{"class":174},[165,1236,1237],{"class":207},": [",[165,1239,310],{"class":309},[165,1241,285],{"class":207},[165,1243,321],{"class":309},[165,1245,285],{"class":207},[165,1247,331],{"class":309},[165,1249,285],{"class":207},[165,1251,348],{"class":309},[165,1253,1254],{"class":207},"], ",[165,1256,288],{"class":174},[165,1258,1237],{"class":207},[165,1260,315],{"class":309},[165,1262,285],{"class":207},[165,1264,326],{"class":309},[165,1266,285],{"class":207},[165,1268,336],{"class":309},[165,1270,285],{"class":207},[165,1272,353],{"class":309},[165,1274,1275],{"class":207},"]})\n",[165,1277,1278,1281,1284,1286,1289,1291,1294,1296,1299,1301,1304],{"class":58,"line":249},[165,1279,1280],{"class":207},"df.to_excel(",[165,1282,1283],{"class":174},"\"campaigns.xlsx\"",[165,1285,285],{"class":207},[165,1287,1288],{"class":461},"index",[165,1290,243],{"class":203},[165,1292,1293],{"class":309},"False",[165,1295,285],{"class":207},[165,1297,1298],{"class":461},"sheet_name",[165,1300,243],{"class":203},[165,1302,1303],{"class":174},"\"Campaigns\"",[165,1305,541],{"class":207},[165,1307,1308],{"class":58,"line":260},[165,1309,234],{"emptyLinePlaceholder":233},[165,1311,1312,1314,1316,1319,1321],{"class":58,"line":271},[165,1313,240],{"class":207},[165,1315,243],{"class":203},[165,1317,1318],{"class":207}," load_workbook(",[165,1320,1283],{"class":174},[165,1322,541],{"class":207},[165,1324,1325,1327,1329,1332,1334],{"class":58,"line":276},[165,1326,252],{"class":207},[165,1328,243],{"class":203},[165,1330,1331],{"class":207}," wb[",[165,1333,1303],{"class":174},[165,1335,1336],{"class":207},"]\n",[165,1338,1339],{"class":58,"line":294},[165,1340,234],{"emptyLinePlaceholder":233},[165,1342,1343,1345,1347],{"class":58,"line":342},[165,1344,393],{"class":207},[165,1346,243],{"class":203},[165,1348,398],{"class":207},[165,1350,1351,1353,1355],{"class":58,"line":379},[165,1352,404],{"class":207},[165,1354,243],{"class":203},[165,1356,1357],{"class":174}," \"Revenue against spend\"\n",[165,1359,1360,1362,1364,1366,1368,1370,1372,1374,1376,1378,1380,1382,1384,1386],{"class":58,"line":385},[165,1361,453],{"class":207},[165,1363,243],{"class":203},[165,1365,458],{"class":207},[165,1367,462],{"class":461},[165,1369,243],{"class":203},[165,1371,467],{"class":309},[165,1373,285],{"class":207},[165,1375,472],{"class":461},[165,1377,243],{"class":203},[165,1379,477],{"class":309},[165,1381,285],{"class":207},[165,1383,482],{"class":461},[165,1385,243],{"class":203},[165,1387,487],{"class":207},[165,1389,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1416],{"class":58,"line":390},[165,1391,493],{"class":207},[165,1393,243],{"class":203},[165,1395,458],{"class":207},[165,1397,462],{"class":461},[165,1399,243],{"class":203},[165,1401,477],{"class":309},[165,1403,285],{"class":207},[165,1405,472],{"class":461},[165,1407,243],{"class":203},[165,1409,467],{"class":309},[165,1411,285],{"class":207},[165,1413,482],{"class":461},[165,1415,243],{"class":203},[165,1417,487],{"class":207},[165,1419,1420,1423,1425,1427,1429],{"class":58,"line":401},[165,1421,1422],{"class":207},"chart.series.append(Series(y_values, x_values, ",[165,1424,533],{"class":461},[165,1426,243],{"class":203},[165,1428,538],{"class":309},[165,1430,1431],{"class":207},"))\n",[165,1433,1434,1436,1438],{"class":58,"line":412},[165,1435,558],{"class":207},[165,1437,561],{"class":174},[165,1439,541],{"class":207},[165,1441,1442,1444,1446],{"class":58,"line":423},[165,1443,569],{"class":207},[165,1445,1283],{"class":174},[165,1447,541],{"class":207},[10,1449,1450,1451,1454],{},"Writing with pandas and charting with openpyxl afterwards is the normal division of labour, since ",[162,1452,1453],{},"to_excel"," has no chart support of its own.",[150,1456,1458],{"id":1457},"style-the-chart-to-match-the-report","Style the chart to match the report",[10,1460,1461],{},"A generated chart that clashes with the workbook's palette looks like it came from somewhere else. openpyxl exposes the underlying drawing properties, so the marker and trendline colours can follow the same theme as the rest of the report:",[155,1463,1465],{"className":188,"code":1464,"language":190,"meta":160,"style":160},"from openpyxl.chart.marker import Marker\nfrom openpyxl.drawing.fill import PatternFillProperties, ColorChoice\nfrom openpyxl.drawing.line import LineProperties\n\nBRAND = \"5B5CF0\"\nACCENT = \"0F9488\"\n\nseries.marker = Marker(symbol=\"circle\", size=8)\nseries.marker.graphicalProperties.solidFill = BRAND\nseries.marker.graphicalProperties.line.solidFill = BRAND\nseries.graphicalProperties.line.noFill = True\n\nseries.trendline.graphicalProperties = series.trendline.graphicalProperties or None\nchart.style = None                      # stop Excel's own style overriding the colours\n",[162,1466,1467,1477,1489,1501,1505,1516,1526,1530,1555,1565,1574,1582,1586,1602],{"__ignoreMap":160},[165,1468,1469,1471,1473,1475],{"class":58,"line":167},[165,1470,204],{"class":203},[165,1472,604],{"class":207},[165,1474,211],{"class":203},[165,1476,609],{"class":207},[165,1478,1479,1481,1484,1486],{"class":58,"line":200},[165,1480,204],{"class":203},[165,1482,1483],{"class":207}," openpyxl.drawing.fill ",[165,1485,211],{"class":203},[165,1487,1488],{"class":207}," PatternFillProperties, ColorChoice\n",[165,1490,1491,1493,1496,1498],{"class":58,"line":217},[165,1492,204],{"class":203},[165,1494,1495],{"class":207}," openpyxl.drawing.line ",[165,1497,211],{"class":203},[165,1499,1500],{"class":207}," LineProperties\n",[165,1502,1503],{"class":58,"line":230},[165,1504,234],{"emptyLinePlaceholder":233},[165,1506,1507,1510,1513],{"class":58,"line":237},[165,1508,1509],{"class":309},"BRAND",[165,1511,1512],{"class":203}," =",[165,1514,1515],{"class":174}," \"5B5CF0\"\n",[165,1517,1518,1521,1523],{"class":58,"line":249},[165,1519,1520],{"class":309},"ACCENT",[165,1522,1512],{"class":203},[165,1524,1525],{"class":174}," \"0F9488\"\n",[165,1527,1528],{"class":58,"line":260},[165,1529,234],{"emptyLinePlaceholder":233},[165,1531,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550,1553],{"class":58,"line":271},[165,1533,618],{"class":207},[165,1535,243],{"class":203},[165,1537,623],{"class":207},[165,1539,626],{"class":461},[165,1541,243],{"class":203},[165,1543,631],{"class":174},[165,1545,285],{"class":207},[165,1547,636],{"class":461},[165,1549,243],{"class":203},[165,1551,1552],{"class":309},"8",[165,1554,541],{"class":207},[165,1556,1557,1560,1562],{"class":58,"line":276},[165,1558,1559],{"class":207},"series.marker.graphicalProperties.solidFill ",[165,1561,243],{"class":203},[165,1563,1564],{"class":309}," BRAND\n",[165,1566,1567,1570,1572],{"class":58,"line":294},[165,1568,1569],{"class":207},"series.marker.graphicalProperties.line.solidFill ",[165,1571,243],{"class":203},[165,1573,1564],{"class":309},[165,1575,1576,1578,1580],{"class":58,"line":342},[165,1577,648],{"class":207},[165,1579,243],{"class":203},[165,1581,1148],{"class":309},[165,1583,1584],{"class":58,"line":379},[165,1585,234],{"emptyLinePlaceholder":233},[165,1587,1588,1591,1593,1596,1599],{"class":58,"line":385},[165,1589,1590],{"class":207},"series.trendline.graphicalProperties ",[165,1592,243],{"class":203},[165,1594,1595],{"class":207}," series.trendline.graphicalProperties ",[165,1597,1598],{"class":203},"or",[165,1600,1601],{"class":309}," None\n",[165,1603,1604,1606,1608,1611],{"class":58,"line":390},[165,1605,415],{"class":207},[165,1607,243],{"class":203},[165,1609,1610],{"class":309}," None",[165,1612,1613],{"class":656},"                      # stop Excel's own style overriding the colours\n",[10,1615,1616,1617,1620,1621,18],{},"Setting ",[162,1618,1619],{},"chart.style = None"," matters: an Excel chart style can override explicit colours, so a chart that looked right in code comes out in the default blue. Define the hex codes once in a constants module and reuse them across every chart and cell format in the report — the approach in ",[14,1622,1624],{"href":1623},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-a-reusable-style-theme-across-an-excel-report\u002F","Apply a reusable style theme across an Excel report",[150,1626,1628],{"id":1627},"label-the-interesting-points","Label the interesting points",[10,1630,1631],{},"A scatter chart with forty unlabelled points invites the question \"which one is that?\". Excel can label every point, which is usually too much, so label only the outliers by putting their names in a helper column and adding a second, labelled series:",[155,1633,1635],{"className":188,"code":1634,"language":190,"meta":160,"style":160},"from openpyxl.chart.label import DataLabelList\n\nseries.dLbls = DataLabelList()\nseries.dLbls.showVal = False\nseries.dLbls.showSerName = False\nseries.dLbls.showCatName = True     # the x-value column supplies the name\n",[162,1636,1637,1649,1653,1663,1673,1682],{"__ignoreMap":160},[165,1638,1639,1641,1644,1646],{"class":58,"line":167},[165,1640,204],{"class":203},[165,1642,1643],{"class":207}," openpyxl.chart.label ",[165,1645,211],{"class":203},[165,1647,1648],{"class":207}," DataLabelList\n",[165,1650,1651],{"class":58,"line":200},[165,1652,234],{"emptyLinePlaceholder":233},[165,1654,1655,1658,1660],{"class":58,"line":217},[165,1656,1657],{"class":207},"series.dLbls ",[165,1659,243],{"class":203},[165,1661,1662],{"class":207}," DataLabelList()\n",[165,1664,1665,1668,1670],{"class":58,"line":230},[165,1666,1667],{"class":207},"series.dLbls.showVal ",[165,1669,243],{"class":203},[165,1671,1672],{"class":309}," False\n",[165,1674,1675,1678,1680],{"class":58,"line":237},[165,1676,1677],{"class":207},"series.dLbls.showSerName ",[165,1679,243],{"class":203},[165,1681,1672],{"class":309},[165,1683,1684,1687,1689,1691],{"class":58,"line":249},[165,1685,1686],{"class":207},"series.dLbls.showCatName ",[165,1688,243],{"class":203},[165,1690,653],{"class":309},[165,1692,1693],{"class":656},"     # the x-value column supplies the name\n",[10,1695,1696],{},"For a genuinely selective approach, split the data into two series — \"typical\" and \"notable\" — and enable labels on the second only. That also lets the notable points carry a different marker colour, which does more for comprehension than any amount of labelling.",[20,1698,29,1703,29,1706,29,1709,29,1712,29,1715,29,1718,29,1720,29,1725,29,1729,29,1731,29,1735,29,1738],{"viewBox":1699,"role":23,"ariaLabelledBy":1700,"xmlns":27,"style":28},"0 0 760 200",[1701,1702],"sc3-t","sc3-d",[31,1704,1705],{"id":1701},"Labelling every point versus labelling the notable ones",[35,1707,1708],{"id":1702},"Labels on all points overlap into an unreadable cluster, while splitting the data into a plain series and a highlighted series labels only what the reader needs to identify.",[39,1710],{"x":41,"y":41,"width":42,"height":1711,"fill":44},"200",[46,1713,1714],{"x":48,"y":49,"style":55},"every point labelled",[46,1716,1717],{"x":54,"y":49,"style":50},"two series, one labelled",[39,1719],{"x":67,"y":68,"width":69,"height":91,"rx":71,"fill":113,"stroke":63},[46,1721,1724],{"x":48,"y":1722,"style":1723},"88","font-size:12px;fill:var(--accent-ink,#be185d);text-anchor:middle","labels collide and overlap",[46,1726,1728],{"x":48,"y":1727,"style":1723},"118","the reader gives up",[39,1730],{"x":112,"y":68,"width":69,"height":91,"rx":71,"fill":72,"stroke":63},[46,1732,1734],{"x":1733,"y":1722,"style":800},"564","plain markers for the cluster",[46,1736,1737],{"x":1733,"y":1727,"style":800},"named markers for the outliers",[46,1739,1741],{"x":60,"y":1740,"style":823},"184","The second version answers the question the chart was drawn to raise",[150,1743,1745],{"id":1744},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[1747,1748,1749,1761,1770,1784,1794],"ul",{},[1750,1751,1752,1756,1757,1760],"li",{},[1753,1754,1755],"strong",{},"Lines you did not ask for."," Set ",[162,1758,1759],{},"line.noFill = True"," on every series in a scatter chart.",[1750,1762,1763,1766,1767,1769],{},[1753,1764,1765],{},"Off-by-one ranges."," The y reference includes the header when ",[162,1768,584],{},"; the x reference does not.",[1750,1771,1772,1775,1776,1779,1780,1783],{},[1753,1773,1774],{},"Text in a numeric column."," A single ",[162,1777,1778],{},"\"n\u002Fa\""," makes Excel drop the point silently. Coerce with ",[162,1781,1782],{},"pd.to_numeric(..., errors=\"coerce\")"," first.",[1750,1785,1786,1789,1790,1793],{},[1753,1787,1788],{},"A chart anchored over the data."," ",[162,1791,1792],{},"add_chart(chart, \"D2\")"," places the top-left corner there; pick a cell clear of the table.",[1750,1795,1796,1799,1800,18],{},[1753,1797,1798],{},"Charts lost on a template round trip."," openpyxl drops charts it did not create when re-saving some files — see ",[14,1801,1803],{"href":1802},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fkeep-charts-and-images-when-filling-an-excel-template\u002F","Keep charts and images when filling an Excel template",[150,1805,1807],{"id":1806},"performance-and-scale-notes","Performance and scale notes",[10,1809,1810,1811,18],{},"A native chart stores references, not pixels, so the file grows by a few kilobytes regardless of how many points it plots. Excel's rendering is the limit instead: a scatter series of more than a few thousand points is slow to draw and impossible to read. Aggregate or sample before charting — a hex-bin style summary, or a sample plus a trendline over the full data — and keep the raw rows on a separate sheet. If the visual is genuinely dense, render it with matplotlib and embed the image instead, as covered in ",[14,1812,1814],{"href":1813},"\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",[150,1816,1818],{"id":1817},"conclusion","Conclusion",[10,1820,1821,1822,1824,1825,1828,1829,1832],{},"A scatter chart takes two ",[162,1823,580],{}," ranges, a ",[162,1826,1827],{},"Series"," and an ",[162,1830,1831],{},"add_chart"," call — with the header row included in the y range and excluded from the x range. Turn off the connecting line, name both axes, anchor the scales when reports must be comparable, and add a trendline with its R² when the chart is arguing for a relationship. Above a few thousand points, aggregate first or embed an image instead.",[150,1834,1836],{"id":1835},"frequently-asked-questions","Frequently asked questions",[10,1838,1839,1842,1843,1846],{},[1753,1840,1841],{},"Why does my scatter chart draw lines between the points?","\nopenpyxl's default series style joins the markers. Set ",[162,1844,1845],{},"series.graphicalProperties.line.noFill = True"," and give the series an explicit marker symbol so only the points render.",[10,1848,1849,1852,1853,1855,1856,1858],{},[1753,1850,1851],{},"Why is the chart empty when I open the file?","\nUsually the ",[162,1854,580],{}," ranges point at the wrong rows — remember the y range includes the header only when ",[162,1857,584],{}," — or the values column contains text rather than numbers.",[10,1860,1861,1864],{},[1753,1862,1863],{},"What is the difference between a scatter chart and a line chart?","\nA scatter chart plots numeric x against numeric y, so the horizontal spacing reflects the values. A line chart treats the categories as evenly spaced labels, which misrepresents uneven intervals.",[10,1866,1867,1870,1871,1874],{},[1753,1868,1869],{},"Can I add a trendline?","\nYes. Assign a ",[162,1872,1873],{},"Trendline"," object to the series — openpyxl supports linear, polynomial, exponential and moving-average types, and Excel computes it on open.",[10,1876,1877,1880,1881,675,1884,1887,1888,1891],{},[1753,1878,1879],{},"How do I control the axis range?","\nSet ",[162,1882,1883],{},"chart.x_axis.scaling.min",[162,1885,1886],{},".max"," (and the same on ",[162,1889,1890],{},"y_axis","). Leaving them unset lets Excel choose, which is usually fine but can hide an outlier's context.",[150,1893,1895],{"id":1894},"related","Related",[1747,1897,1898,1906,1913,1920,1927],{},[1750,1899,1900,1789,1903,1905],{},[1753,1901,1902],{},"Up:",[14,1904,17],{"href":16}," — the full chart vocabulary.",[1750,1907,1908,1912],{},[14,1909,1911],{"href":1910},"\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"," — the categorical counterpart.",[1750,1914,1915,1919],{},[14,1916,1918],{"href":1917},"\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"," — when the x axis really is a sequence.",[1750,1921,1922,1926],{},[14,1923,1925],{"href":1924},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-combo-chart-with-a-secondary-axis-in-openpyxl\u002F","Add a combo chart with a secondary axis in openpyxl"," — two measures at different scales on one chart.",[1750,1928,1929,1931],{},[14,1930,1814],{"href":1813}," — for visuals Excel's own charts cannot draw.",[1933,1934,1935],"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}",{"title":160,"searchDepth":200,"depth":200,"links":1937},[1938,1939,1940,1941,1942,1943,1944,1945,1946,1947,1948,1949,1950,1951],{"id":152,"depth":200,"text":153},{"id":181,"depth":200,"text":182},{"id":588,"depth":200,"text":589},{"id":682,"depth":200,"text":683},{"id":831,"depth":200,"text":832},{"id":929,"depth":200,"text":930},{"id":1175,"depth":200,"text":1176},{"id":1457,"depth":200,"text":1458},{"id":1627,"depth":200,"text":1628},{"id":1744,"depth":200,"text":1745},{"id":1806,"depth":200,"text":1807},{"id":1817,"depth":200,"text":1818},{"id":1835,"depth":200,"text":1836},{"id":1894,"depth":200,"text":1895},"2026-08-27","Plot one numeric column against another with openpyxl: ScatterChart and Series, markers without lines, axis titles and scaling, a trendline, and multiple series.","md",[1956,1958,1960,1962,1964],{"q":1841,"a":1957},"openpyxl's default series style joins the markers. Set series.graphicalProperties.line.noFill = True and give the series an explicit marker symbol so only the points render.",{"q":1851,"a":1959},"Usually the Reference ranges point at the wrong rows — remember min_row is inclusive and should skip the header when you pass titles_from_data=False — or the values column contains text rather than numbers.",{"q":1863,"a":1961},"A scatter chart plots numeric x against numeric y, so the horizontal spacing reflects the values. A line chart treats the categories as evenly spaced labels, which misrepresents uneven intervals.",{"q":1869,"a":1963},"Yes. Assign a Trendline object to the series — openpyxl supports linear, polynomial, exponential and moving-average types, and Excel computes it on open.",{"q":1879,"a":1965},"Set chart.x_axis.scaling.min and .max (and the same on y_axis). Leaving them unset lets Excel choose, which is usually fine but can hide an outlier's context.",{},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl",{"title":1969,"description":1970},"Excel Scatter Chart with openpyxl","Build a scatter chart in openpyxl: ScatterChart, Reference ranges, marker-only series, axis titles and min\u002Fmax, trendlines, several series, and common empty-chart fixes.","create-a-scatter-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl\u002Findex","how-to","YVzvg5TQRxccT0nNNINWwsle8_xNWw3lCWSCfW6y8o4",[1976,1980],{"title":1977,"path":1978,"stem":1979,"children":-1},"Create a Pie Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-pie-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-pie-chart-in-excel-with-openpyxl\u002Findex",{"title":1981,"path":1982,"stem":1983,"children":-1},"Create a Bar Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl\u002Findex",1788710162841]