[{"data":1,"prerenderedAt":2287},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl":2253,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl":2262,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl":2267,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl":2268},{"id":4,"title":5,"body":6,"dateModified":2220,"datePublished":2220,"description":2221,"extension":2222,"faq":2223,"keyword":2232,"kind":2233,"meta":2234,"navigation":301,"path":2242,"seo":2243,"slug":2246,"stem":2247,"topics":2248,"type":2251,"__hash__":2252},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl\u002Findex.md","Format Chart Axes with openpyxl",{"type":7,"value":8,"toc":2206},"minimark",[9,36,205,210,235,238,242,260,683,690,694,697,770,792,796,799,928,950,954,962,1137,1140,1262,1266,1269,1523,1538,1549,1800,1811,2005,2009,2028,2032,2091,2095,2098,2102,2143,2147,2153,2159,2165,2171,2175,2202],[10,11,12,13,17,18,21,22,25,26,29,30,35],"p",{},"A chart's axes decide how it is read. An axis that starts at an arbitrary value exaggerates small changes; labels such as ",[14,15,16],"code",{},"45000"," next to ",[14,19,20],{},"50000"," are harder to scan than ",[14,23,24],{},"45k"," and ",[14,27,28],{},"50k","; category labels that overlap become a smudge. openpyxl exposes every axis setting Excel has, but not always under obvious names, and one default — axes that Excel does not show at all — catches almost everyone. This page, part of ",[31,32,34],"a",{"href":33},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",", goes through the settings that matter in reports, from making the axes visible to rotating crowded labels.",[37,38,46,47,46,51,46,55,46,63,46,71,46,78,46,85,46,88,46,92,46,97,46,103,46,105,46,110,46,112,46,115,46,118,46,122,46,124,46,128,46,130,46,133,46,136,46,140,46,142,46,146,46,148,46,151,46,154,46,158,46,160,46,164,46,166,46,169,46,172,46,176,46,178,46,182,46,184,46,187,46,190,46,194,46,196,46,200,46,202],"svg",{"viewBox":39,"role":40,"ariaLabelledBy":41,"xmlns":44,"style":45},"4 0 752 276","img",[42,43],"ax-map-t","ax-map-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  ",[48,49,50],"title",{"id":42},"Where each axis setting lives in openpyxl",[52,53,54],"desc",{"id":43},"A table mapping axis settings to openpyxl attributes. Visibility is axis.delete, which must be False. The title is axis.title. The range is axis.scaling.min and max, and the step is majorUnit. Label format is number_format. Gridlines are majorGridlines and minorGridlines. A log scale is scaling.logBase. Label rotation is txPr. Label position is tickLblPos.",[56,57],"rect",{"x":58,"y":59,"width":60,"height":61,"fill":62},"4","0","752","276","#ffffff",[56,64],{"x":65,"y":66,"width":67,"height":68,"rx":59,"fill":62,"stroke":69,"style":70},"20.0","16.0","127.1","40.0","var(--line,#cdd5e6)","stroke-width:1px",[56,72],{"x":73,"y":66,"width":74,"height":68,"rx":59,"fill":75,"stroke":76,"style":77},"147.1","296.5","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[79,80,84],"text",{"x":81,"y":82,"style":83},"295.3","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","openpyxl attribute",[56,86],{"x":87,"y":66,"width":74,"height":68,"rx":59,"fill":75,"stroke":76,"style":77},"443.5",[79,89,91],{"x":90,"y":82,"style":83},"591.8","Example",[56,93],{"x":65,"y":94,"width":67,"height":95,"rx":59,"fill":96,"stroke":69,"style":70},"56.0","34.0","#e7ebef",[79,98,102],{"x":99,"y":100,"style":101},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Visible in Excel",[56,104],{"x":73,"y":94,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,106,109],{"x":81,"y":107,"style":108},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","axis.delete",[56,111],{"x":87,"y":94,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,113,114],{"x":90,"y":107,"style":108},"False",[56,116],{"x":65,"y":117,"width":67,"height":95,"rx":59,"fill":96,"stroke":69,"style":70},"90.0",[79,119,121],{"x":99,"y":120,"style":101},"112.0","Range and step",[56,123],{"x":73,"y":117,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,125,127],{"x":81,"y":126,"style":108},"111.0","scaling.min \u002F max, majorUnit",[56,129],{"x":87,"y":117,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,131,132],{"x":90,"y":126,"style":108},"0, 100, 20",[56,134],{"x":65,"y":135,"width":67,"height":95,"rx":59,"fill":96,"stroke":69,"style":70},"124.0",[79,137,139],{"x":99,"y":138,"style":101},"146.0","Label format",[56,141],{"x":73,"y":135,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,143,145],{"x":81,"y":144,"style":108},"145.0","number_format",[56,147],{"x":87,"y":135,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,149,150],{"x":90,"y":144,"style":108},"#,##0,\"k\"",[56,152],{"x":65,"y":153,"width":67,"height":95,"rx":59,"fill":96,"stroke":69,"style":70},"158.0",[79,155,157],{"x":99,"y":156,"style":101},"180.0","Gridlines",[56,159],{"x":73,"y":153,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,161,163],{"x":81,"y":162,"style":108},"179.0","majorGridlines",[56,165],{"x":87,"y":153,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,167,168],{"x":90,"y":162,"style":108},"None to remove",[56,170],{"x":65,"y":171,"width":67,"height":95,"rx":59,"fill":96,"stroke":69,"style":70},"192.0",[79,173,175],{"x":99,"y":174,"style":101},"214.0","Log scale",[56,177],{"x":73,"y":171,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,179,181],{"x":81,"y":180,"style":108},"213.0","scaling.logBase",[56,183],{"x":87,"y":171,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,185,186],{"x":90,"y":180,"style":108},"10",[56,188],{"x":65,"y":189,"width":67,"height":95,"rx":59,"fill":96,"stroke":69,"style":70},"226.0",[79,191,193],{"x":99,"y":192,"style":101},"248.0","Label angle",[56,195],{"x":73,"y":189,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,197,199],{"x":81,"y":198,"style":108},"247.0","txPr",[56,201],{"x":87,"y":189,"width":74,"height":95,"rx":59,"fill":62,"stroke":69,"style":70},[79,203,204],{"x":90,"y":198,"style":108},"rot=-2700000",[206,207,209],"h2",{"id":208},"prerequisites","Prerequisites",[211,212,217],"pre",{"className":213,"code":214,"language":215,"meta":216,"style":216},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[14,218,219],{"__ignoreMap":216},[220,221,224,228,232],"span",{"class":222,"line":223},"line",1,[220,225,227],{"class":226},"sMTad","pip",[220,229,231],{"class":230},"srMev"," install",[220,233,234],{"class":230}," openpyxl\n",[10,236,237],{},"The examples chart twelve weeks of website visits, which grow quickly, and a weekly margin figure that starts negative.",[206,239,241],{"id":240},"step-1-make-the-axes-visible","Step 1 — Make the axes visible",[10,243,244,245,248,249,252,253,256,257,259],{},"In openpyxl charts the horizontal axis is ",[14,246,247],{},"chart.x_axis"," and the vertical one ",[14,250,251],{},"chart.y_axis"," (for bar charts, which are horizontal, the names stay the same but the directions swap). Each axis has a ",[14,254,255],{},"delete"," attribute; openpyxl 3.1 leaves it unset, and Excel treats an unset value as \"hidden\". Set both to ",[14,258,114],{}," on every chart:",[211,261,265],{"className":262,"code":263,"language":264,"meta":216,"style":216},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\nfrom openpyxl.chart import BarChart, LineChart, Reference\n\nwb = Workbook(); ws = wb.active; ws.title = \"Weekly\"\nws.append([\"Week\", \"Visits\", \"Margin %\"])\nfor i in range(1, 13):\n    ws.append([f\"Week {i:02d}\", int(1200 * 1.35 ** i), round(-4 + i * 0.9, 1)])\n\ndef line_chart(title):\n    chart = LineChart()\n    chart.title = title\n    chart.add_data(Reference(ws, min_col=2, min_row=1, max_row=13), titles_from_data=True)\n    chart.set_categories(Reference(ws, min_col=1, min_row=2, max_row=13))\n    chart.x_axis.delete = False\n    chart.y_axis.delete = False\n    chart.legend = None\n    chart.width, chart.height = 18, 8\n    return chart\n\nvisits = line_chart(\"Weekly visits\")\nvisits.x_axis.title = \"Week\"\nvisits.y_axis.title = \"Visits\"\nprint(visits.x_axis.delete, visits.y_axis.delete)\n","python",[14,266,267,283,296,303,325,348,378,456,461,474,485,496,543,574,585,595,606,622,631,636,652,663,674],{"__ignoreMap":216},[220,268,269,273,277,280],{"class":222,"line":223},[220,270,272],{"class":271},"s-kum","from",[220,274,276],{"class":275},"skGVy"," openpyxl ",[220,278,279],{"class":271},"import",[220,281,282],{"class":275}," Workbook\n",[220,284,286,288,291,293],{"class":222,"line":285},2,[220,287,272],{"class":271},[220,289,290],{"class":275}," openpyxl.chart ",[220,292,279],{"class":271},[220,294,295],{"class":275}," BarChart, LineChart, Reference\n",[220,297,299],{"class":222,"line":298},3,[220,300,302],{"emptyLinePlaceholder":301},true,"\n",[220,304,306,309,312,315,317,320,322],{"class":222,"line":305},4,[220,307,308],{"class":275},"wb ",[220,310,311],{"class":271},"=",[220,313,314],{"class":275}," Workbook(); ws ",[220,316,311],{"class":271},[220,318,319],{"class":275}," wb.active; ws.title ",[220,321,311],{"class":271},[220,323,324],{"class":230}," \"Weekly\"\n",[220,326,328,331,334,337,340,342,345],{"class":222,"line":327},5,[220,329,330],{"class":275},"ws.append([",[220,332,333],{"class":230},"\"Week\"",[220,335,336],{"class":275},", ",[220,338,339],{"class":230},"\"Visits\"",[220,341,336],{"class":275},[220,343,344],{"class":230},"\"Margin %\"",[220,346,347],{"class":275},"])\n",[220,349,351,354,357,360,364,367,370,372,375],{"class":222,"line":350},6,[220,352,353],{"class":271},"for",[220,355,356],{"class":275}," i ",[220,358,359],{"class":271},"in",[220,361,363],{"class":362},"sP0c6"," range",[220,365,366],{"class":275},"(",[220,368,369],{"class":362},"1",[220,371,336],{"class":275},[220,373,374],{"class":362},"13",[220,376,377],{"class":275},"):\n",[220,379,381,384,387,390,394,397,400,403,406,408,411,413,416,419,422,425,428,431,433,436,438,441,443,446,449,451,453],{"class":222,"line":380},7,[220,382,383],{"class":275},"    ws.append([",[220,385,386],{"class":271},"f",[220,388,389],{"class":230},"\"Week ",[220,391,393],{"class":392},"sSjpA","{",[220,395,396],{"class":275},"i",[220,398,399],{"class":271},":02d",[220,401,402],{"class":392},"}",[220,404,405],{"class":230},"\"",[220,407,336],{"class":275},[220,409,410],{"class":362},"int",[220,412,366],{"class":275},[220,414,415],{"class":362},"1200",[220,417,418],{"class":271}," *",[220,420,421],{"class":362}," 1.35",[220,423,424],{"class":271}," **",[220,426,427],{"class":275}," i), ",[220,429,430],{"class":362},"round",[220,432,366],{"class":275},[220,434,435],{"class":271},"-",[220,437,58],{"class":362},[220,439,440],{"class":271}," +",[220,442,356],{"class":275},[220,444,445],{"class":271},"*",[220,447,448],{"class":362}," 0.9",[220,450,336],{"class":275},[220,452,369],{"class":362},[220,454,455],{"class":275},")])\n",[220,457,459],{"class":222,"line":458},8,[220,460,302],{"emptyLinePlaceholder":301},[220,462,464,467,471],{"class":222,"line":463},9,[220,465,466],{"class":271},"def",[220,468,470],{"class":469},"s_Opv"," line_chart",[220,472,473],{"class":275},"(title):\n",[220,475,477,480,482],{"class":222,"line":476},10,[220,478,479],{"class":275},"    chart ",[220,481,311],{"class":271},[220,483,484],{"class":275}," LineChart()\n",[220,486,488,491,493],{"class":222,"line":487},11,[220,489,490],{"class":275},"    chart.title ",[220,492,311],{"class":271},[220,494,495],{"class":275}," title\n",[220,497,499,502,506,508,511,513,516,518,520,522,525,527,529,532,535,537,540],{"class":222,"line":498},12,[220,500,501],{"class":275},"    chart.add_data(Reference(ws, ",[220,503,505],{"class":504},"sa561","min_col",[220,507,311],{"class":271},[220,509,510],{"class":362},"2",[220,512,336],{"class":275},[220,514,515],{"class":504},"min_row",[220,517,311],{"class":271},[220,519,369],{"class":362},[220,521,336],{"class":275},[220,523,524],{"class":504},"max_row",[220,526,311],{"class":271},[220,528,374],{"class":362},[220,530,531],{"class":275},"), ",[220,533,534],{"class":504},"titles_from_data",[220,536,311],{"class":271},[220,538,539],{"class":362},"True",[220,541,542],{"class":275},")\n",[220,544,546,549,551,553,555,557,559,561,563,565,567,569,571],{"class":222,"line":545},13,[220,547,548],{"class":275},"    chart.set_categories(Reference(ws, ",[220,550,505],{"class":504},[220,552,311],{"class":271},[220,554,369],{"class":362},[220,556,336],{"class":275},[220,558,515],{"class":504},[220,560,311],{"class":271},[220,562,510],{"class":362},[220,564,336],{"class":275},[220,566,524],{"class":504},[220,568,311],{"class":271},[220,570,374],{"class":362},[220,572,573],{"class":275},"))\n",[220,575,577,580,582],{"class":222,"line":576},14,[220,578,579],{"class":275},"    chart.x_axis.delete ",[220,581,311],{"class":271},[220,583,584],{"class":362}," False\n",[220,586,588,591,593],{"class":222,"line":587},15,[220,589,590],{"class":275},"    chart.y_axis.delete ",[220,592,311],{"class":271},[220,594,584],{"class":362},[220,596,598,601,603],{"class":222,"line":597},16,[220,599,600],{"class":275},"    chart.legend ",[220,602,311],{"class":271},[220,604,605],{"class":362}," None\n",[220,607,609,612,614,617,619],{"class":222,"line":608},17,[220,610,611],{"class":275},"    chart.width, chart.height ",[220,613,311],{"class":271},[220,615,616],{"class":362}," 18",[220,618,336],{"class":275},[220,620,621],{"class":362},"8\n",[220,623,625,628],{"class":222,"line":624},18,[220,626,627],{"class":271},"    return",[220,629,630],{"class":275}," chart\n",[220,632,634],{"class":222,"line":633},19,[220,635,302],{"emptyLinePlaceholder":301},[220,637,639,642,644,647,650],{"class":222,"line":638},20,[220,640,641],{"class":275},"visits ",[220,643,311],{"class":271},[220,645,646],{"class":275}," line_chart(",[220,648,649],{"class":230},"\"Weekly visits\"",[220,651,542],{"class":275},[220,653,655,658,660],{"class":222,"line":654},21,[220,656,657],{"class":275},"visits.x_axis.title ",[220,659,311],{"class":271},[220,661,662],{"class":230}," \"Week\"\n",[220,664,666,669,671],{"class":222,"line":665},22,[220,667,668],{"class":275},"visits.y_axis.title ",[220,670,311],{"class":271},[220,672,673],{"class":230}," \"Visits\"\n",[220,675,677,680],{"class":222,"line":676},23,[220,678,679],{"class":362},"print",[220,681,682],{"class":275},"(visits.x_axis.delete, visits.y_axis.delete)\n",[10,684,685,686,689],{},"LibreOffice ignores the missing flag and draws the axes, so a chart checked only in LibreOffice looks fine; in Excel the same chart shows plot lines floating with no scale. A small helper that creates every chart with ",[14,687,688],{},"delete = False"," removes the problem for good. Axis titles are plain strings; openpyxl turns them into chart text.",[206,691,693],{"id":692},"step-2-fix-the-scale-and-the-label-format","Step 2 — Fix the scale and the label format",[10,695,696],{},"By default Excel chooses the range and step. Set them when the default misleads — a range that does not start at zero for a measure where zero matters, or steps that fall on awkward numbers — and set a number format so the labels are easy to read:",[211,698,700],{"className":262,"code":699,"language":264,"meta":216,"style":216},"visits.y_axis.scaling.min = 0\nvisits.y_axis.scaling.max = 50000\nvisits.y_axis.majorUnit = 10000\nvisits.y_axis.number_format = '#,##0,\"k\"'\nws.add_chart(visits, \"E2\")\nprint(ws[\"B13\"].value, \"visits in week 12\")\n",[14,701,702,712,722,732,742,752],{"__ignoreMap":216},[220,703,704,707,709],{"class":222,"line":223},[220,705,706],{"class":275},"visits.y_axis.scaling.min ",[220,708,311],{"class":271},[220,710,711],{"class":362}," 0\n",[220,713,714,717,719],{"class":222,"line":285},[220,715,716],{"class":275},"visits.y_axis.scaling.max ",[220,718,311],{"class":271},[220,720,721],{"class":362}," 50000\n",[220,723,724,727,729],{"class":222,"line":298},[220,725,726],{"class":275},"visits.y_axis.majorUnit ",[220,728,311],{"class":271},[220,730,731],{"class":362}," 10000\n",[220,733,734,737,739],{"class":222,"line":305},[220,735,736],{"class":275},"visits.y_axis.number_format ",[220,738,311],{"class":271},[220,740,741],{"class":230}," '#,##0,\"k\"'\n",[220,743,744,747,750],{"class":222,"line":327},[220,745,746],{"class":275},"ws.add_chart(visits, ",[220,748,749],{"class":230},"\"E2\"",[220,751,542],{"class":275},[220,753,754,756,759,762,765,768],{"class":222,"line":350},[220,755,679],{"class":362},[220,757,758],{"class":275},"(ws[",[220,760,761],{"class":230},"\"B13\"",[220,763,764],{"class":275},"].value, ",[220,766,767],{"class":230},"\"visits in week 12\"",[220,769,542],{"class":275},[10,771,772,773,775,776,778,779,782,783,786,787,791],{},"The axis now runs from 0 to 50k in steps of 10k. In the format ",[14,774,150],{},", the comma after the last ",[14,777,59],{}," divides the displayed value by 1,000 and ",[14,780,781],{},"\"k\""," adds the suffix; two commas would divide by a million, for ",[14,784,785],{},"\"m\"",". The format applies only to the axis labels, not to the data, so the cells keep their full values. ",[31,788,790],{"href":789},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-numbers-in-thousands-and-millions-in-excel\u002F","Format Numbers in Thousands and Millions in Excel"," explains the scaling commas.",[206,793,795],{"id":794},"step-3-gridlines-on-off-or-finer","Step 3 — Gridlines on, off or finer",[10,797,798],{},"openpyxl gives the value axis major gridlines by default. Remove them for a cleaner chart when there are data labels, or add minor gridlines for a chart people read values from:",[211,800,802],{"className":262,"code":801,"language":264,"meta":216,"style":216},"from openpyxl.chart.axis import ChartLines\n\nclean = line_chart(\"Weekly visits — no gridlines\")\nclean.y_axis.majorGridlines = None\nws.add_chart(clean, \"E20\")\n\nfine = line_chart(\"Weekly visits — minor gridlines\")\nfine.y_axis.majorUnit = 10000\nfine.y_axis.minorUnit = 2500\nfine.y_axis.minorGridlines = ChartLines()\nws.add_chart(fine, \"E38\")\nprint(clean.y_axis.majorGridlines, type(fine.y_axis.minorGridlines).__name__)\n",[14,803,804,816,820,834,843,853,857,871,880,890,900,910],{"__ignoreMap":216},[220,805,806,808,811,813],{"class":222,"line":223},[220,807,272],{"class":271},[220,809,810],{"class":275}," openpyxl.chart.axis ",[220,812,279],{"class":271},[220,814,815],{"class":275}," ChartLines\n",[220,817,818],{"class":222,"line":285},[220,819,302],{"emptyLinePlaceholder":301},[220,821,822,825,827,829,832],{"class":222,"line":298},[220,823,824],{"class":275},"clean ",[220,826,311],{"class":271},[220,828,646],{"class":275},[220,830,831],{"class":230},"\"Weekly visits — no gridlines\"",[220,833,542],{"class":275},[220,835,836,839,841],{"class":222,"line":305},[220,837,838],{"class":275},"clean.y_axis.majorGridlines ",[220,840,311],{"class":271},[220,842,605],{"class":362},[220,844,845,848,851],{"class":222,"line":327},[220,846,847],{"class":275},"ws.add_chart(clean, ",[220,849,850],{"class":230},"\"E20\"",[220,852,542],{"class":275},[220,854,855],{"class":222,"line":350},[220,856,302],{"emptyLinePlaceholder":301},[220,858,859,862,864,866,869],{"class":222,"line":380},[220,860,861],{"class":275},"fine ",[220,863,311],{"class":271},[220,865,646],{"class":275},[220,867,868],{"class":230},"\"Weekly visits — minor gridlines\"",[220,870,542],{"class":275},[220,872,873,876,878],{"class":222,"line":458},[220,874,875],{"class":275},"fine.y_axis.majorUnit ",[220,877,311],{"class":271},[220,879,731],{"class":362},[220,881,882,885,887],{"class":222,"line":463},[220,883,884],{"class":275},"fine.y_axis.minorUnit ",[220,886,311],{"class":271},[220,888,889],{"class":362}," 2500\n",[220,891,892,895,897],{"class":222,"line":476},[220,893,894],{"class":275},"fine.y_axis.minorGridlines ",[220,896,311],{"class":271},[220,898,899],{"class":275}," ChartLines()\n",[220,901,902,905,908],{"class":222,"line":487},[220,903,904],{"class":275},"ws.add_chart(fine, ",[220,906,907],{"class":230},"\"E38\"",[220,909,542],{"class":275},[220,911,912,914,917,920,923,926],{"class":222,"line":498},[220,913,679],{"class":362},[220,915,916],{"class":275},"(clean.y_axis.majorGridlines, ",[220,918,919],{"class":362},"type",[220,921,922],{"class":275},"(fine.y_axis.minorGridlines).",[220,924,925],{"class":362},"__name__",[220,927,542],{"class":275},[10,929,930,931,933,934,937,938,941,942,945,946,949],{},"Setting ",[14,932,163],{}," to ",[14,935,936],{},"None"," removes them; assigning a ",[14,939,940],{},"ChartLines()"," object adds them, on either axis — ",[14,943,944],{},"x_axis.majorGridlines"," draws vertical lines at each category. Minor gridlines follow ",[14,947,948],{},"minorUnit",".",[206,951,953],{"id":952},"step-4-a-logarithmic-scale-for-fast-growth","Step 4 — A logarithmic scale for fast growth",[10,955,956,957,961],{},"Visits grow by 35% a week, so on a linear axis the early weeks are squashed against the bottom and the last few dominate. A logarithmic axis gives equal steps to equal ",[958,959,960],"em",{},"ratios",", and steady percentage growth becomes a straight line:",[211,963,965],{"className":262,"code":964,"language":264,"meta":216,"style":216},"log_chart = line_chart(\"Weekly visits — log scale\")\nlog_chart.y_axis.scaling.logBase = 10\nlog_chart.y_axis.number_format = '#,##0,\"k\"'\nlog_chart.y_axis.title = \"Visits (log scale)\"\nws.add_chart(log_chart, \"E56\")\ngrowth = [round(ws.cell(row=r + 1, column=2).value \u002F ws.cell(row=r, column=2).value, 3) for r in range(2, 13)]\nprint(\"week-on-week ratio:\", min(growth), \"to\", max(growth))\n",[14,966,967,981,991,1000,1010,1020,1109],{"__ignoreMap":216},[220,968,969,972,974,976,979],{"class":222,"line":223},[220,970,971],{"class":275},"log_chart ",[220,973,311],{"class":271},[220,975,646],{"class":275},[220,977,978],{"class":230},"\"Weekly visits — log scale\"",[220,980,542],{"class":275},[220,982,983,986,988],{"class":222,"line":285},[220,984,985],{"class":275},"log_chart.y_axis.scaling.logBase ",[220,987,311],{"class":271},[220,989,990],{"class":362}," 10\n",[220,992,993,996,998],{"class":222,"line":298},[220,994,995],{"class":275},"log_chart.y_axis.number_format ",[220,997,311],{"class":271},[220,999,741],{"class":230},[220,1001,1002,1005,1007],{"class":222,"line":305},[220,1003,1004],{"class":275},"log_chart.y_axis.title ",[220,1006,311],{"class":271},[220,1008,1009],{"class":230}," \"Visits (log scale)\"\n",[220,1011,1012,1015,1018],{"class":222,"line":327},[220,1013,1014],{"class":275},"ws.add_chart(log_chart, ",[220,1016,1017],{"class":230},"\"E56\"",[220,1019,542],{"class":275},[220,1021,1022,1025,1027,1030,1032,1035,1038,1040,1043,1046,1049,1051,1054,1056,1058,1061,1064,1067,1069,1071,1074,1076,1078,1080,1083,1086,1089,1091,1094,1096,1098,1100,1102,1104,1106],{"class":222,"line":350},[220,1023,1024],{"class":275},"growth ",[220,1026,311],{"class":271},[220,1028,1029],{"class":275}," [",[220,1031,430],{"class":362},[220,1033,1034],{"class":275},"(ws.cell(",[220,1036,1037],{"class":504},"row",[220,1039,311],{"class":271},[220,1041,1042],{"class":275},"r ",[220,1044,1045],{"class":271},"+",[220,1047,1048],{"class":362}," 1",[220,1050,336],{"class":275},[220,1052,1053],{"class":504},"column",[220,1055,311],{"class":271},[220,1057,510],{"class":362},[220,1059,1060],{"class":275},").value ",[220,1062,1063],{"class":271},"\u002F",[220,1065,1066],{"class":275}," ws.cell(",[220,1068,1037],{"class":504},[220,1070,311],{"class":271},[220,1072,1073],{"class":275},"r, ",[220,1075,1053],{"class":504},[220,1077,311],{"class":271},[220,1079,510],{"class":362},[220,1081,1082],{"class":275},").value, ",[220,1084,1085],{"class":362},"3",[220,1087,1088],{"class":275},") ",[220,1090,353],{"class":271},[220,1092,1093],{"class":275}," r ",[220,1095,359],{"class":271},[220,1097,363],{"class":362},[220,1099,366],{"class":275},[220,1101,510],{"class":362},[220,1103,336],{"class":275},[220,1105,374],{"class":362},[220,1107,1108],{"class":275},")]\n",[220,1110,1111,1113,1115,1118,1120,1123,1126,1129,1131,1134],{"class":222,"line":380},[220,1112,679],{"class":362},[220,1114,366],{"class":275},[220,1116,1117],{"class":230},"\"week-on-week ratio:\"",[220,1119,336],{"class":275},[220,1121,1122],{"class":362},"min",[220,1124,1125],{"class":275},"(growth), ",[220,1127,1128],{"class":230},"\"to\"",[220,1130,336],{"class":275},[220,1132,1133],{"class":362},"max",[220,1135,1136],{"class":275},"(growth))\n",[10,1138,1139],{},"The ratio between consecutive weeks is constant at 1.35, so the line is straight on the log axis — a slowdown would show as the line bending down, which is invisible on the linear chart until it is dramatic. Label the axis as logarithmic: readers assume linear unless told otherwise. A log axis cannot show zero or negative values.",[37,1141,46,1146,46,1149,46,1152,46,1155,46,1163,46,1174,46,1179,46,1184,46,1189,46,1197,46,1201,46,1206,46,1210,46,1217,46,1221,46,1227,46,1232,46,1236,46,1240,46,1245,46,1248,46,1253,46,1256],{"viewBox":1142,"role":40,"ariaLabelledBy":1143,"xmlns":44,"style":45},"4 0 752 207",[1144,1145],"ax-log-t","ax-log-d",[48,1147,1148],{"id":1144},"The same growth on a linear and a logarithmic axis",[52,1150,1151],{"id":1145},"Two panels. On a linear axis, visits growing 35 percent a week look flat for weeks and then shoot up, and a slowdown only shows once it is severe. On a logarithmic axis the same growth is a straight line, so any change in the growth rate shows immediately as a bend.",[56,1153],{"x":58,"y":59,"width":60,"height":1154,"fill":62},"207",[56,1156],{"x":65,"y":66,"width":1157,"height":1158,"rx":1159,"fill":1160,"stroke":1161,"style":1162},"351.0","175.0","14","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[1164,1165,1167],"g",{"transform":1166},"translate(182.5,29.0) scale(1.083)",[1168,1169],"path",{"d":1170,"fill":1171,"stroke":1172,"style":1173},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[79,1175,1178],{"x":1176,"y":100,"style":1177},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Linear axis",[222,1180],{"x1":1181,"y1":1182,"x2":1183,"y2":1182,"stroke":1161,"style":70},"36.0","91.0","355.0",[79,1185,1188],{"x":1186,"y":126,"style":1187},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","early weeks squashed flat",[1164,1190,1192],{"transform":1191},"translate(38.5,99.5) scale(0.625)",[1168,1193],{"d":1194,"fill":1171,"stroke":1195,"style":1196},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[79,1198,1200],{"x":1186,"y":1199,"style":1187},"137.0","curve hides the growth rate",[1164,1202,1204],{"transform":1203},"translate(38.5,125.5) scale(0.625)",[1168,1205],{"d":1194,"fill":1171,"stroke":1195,"style":1196},[79,1207,1209],{"x":1186,"y":1208,"style":1187},"163.0","familiar to every reader",[1164,1211,1213],{"transform":1212},"translate(38.5,151.5) scale(0.625)",[1168,1214],{"d":1215,"fill":1171,"stroke":1216,"style":1196},"M5 12.5l4.5 4.5L19 7","var(--teal,#0f9488)",[56,1218],{"x":1219,"y":66,"width":1157,"height":1158,"rx":1159,"fill":1220,"stroke":1216,"style":1162},"389.0","#d9f4f1",[1164,1222,1224],{"transform":1223},"translate(551.5,29.0) scale(1.083)",[1168,1225],{"d":1170,"fill":1171,"stroke":1226,"style":1173},"var(--teal-ink,#0b6157)",[79,1228,1231],{"x":1229,"y":100,"style":1230},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Log axis (logBase 10)",[222,1233],{"x1":1234,"y1":1182,"x2":1235,"y2":1182,"stroke":1216,"style":70},"405.0","724.0",[79,1237,1239],{"x":1238,"y":126,"style":1187},"429.0","steady growth is a straight line",[1164,1241,1243],{"transform":1242},"translate(407.5,99.5) scale(0.625)",[1168,1244],{"d":1215,"fill":1171,"stroke":1216,"style":1196},[79,1246,1247],{"x":1238,"y":1199,"style":1187},"slowdowns show as a bend",[1164,1249,1251],{"transform":1250},"translate(407.5,125.5) scale(0.625)",[1168,1252],{"d":1215,"fill":1171,"stroke":1216,"style":1196},[79,1254,1255],{"x":1238,"y":1208,"style":1187},"label it as a log scale",[1164,1257,1259],{"transform":1258},"translate(407.5,151.5) scale(0.625)",[1168,1260],{"d":1261,"fill":1171,"stroke":1161,"style":1196},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[206,1263,1265],{"id":1264},"step-5-rotate-thin-out-and-move-category-labels","Step 5 — Rotate, thin out and move category labels",[10,1267,1268],{},"Twelve labels such as \"Week 01\" overlap in a narrow chart. Rotating them 45 degrees usually fixes it; showing every second label is an alternative. Rotation is set through the axis's text properties, with the angle in 60,000ths of a degree:",[211,1270,1272],{"className":262,"code":1271,"language":264,"meta":216,"style":216},"from openpyxl.chart.text import RichText\nfrom openpyxl.drawing.text import CharacterProperties, Paragraph, ParagraphProperties, RichTextProperties\n\ndef rotated_labels(angle_degrees, size_pt=8):\n    return RichText(bodyPr=RichTextProperties(rot=int(angle_degrees * 60000), vert=\"horz\"),\n                    p=[Paragraph(pPr=ParagraphProperties(defRPr=CharacterProperties(sz=size_pt * 100)),\n                                 endParaRPr=CharacterProperties())])\n\nnarrow = line_chart(\"Rotated category labels\")\nnarrow.width = 12\nnarrow.x_axis.txPr = rotated_labels(-45)\nws.add_chart(narrow, \"E74\")\n\nsparse = line_chart(\"Every second label\")\nsparse.width = 12\nsparse.x_axis.tickLblSkip = 2\nws.add_chart(sparse, \"E92\")\nprint(narrow.x_axis.txPr.bodyPr.rot, sparse.x_axis.tickLblSkip)\n",[14,1273,1274,1286,1298,1302,1319,1362,1404,1414,1418,1432,1442,1459,1469,1473,1487,1496,1506,1516],{"__ignoreMap":216},[220,1275,1276,1278,1281,1283],{"class":222,"line":223},[220,1277,272],{"class":271},[220,1279,1280],{"class":275}," openpyxl.chart.text ",[220,1282,279],{"class":271},[220,1284,1285],{"class":275}," RichText\n",[220,1287,1288,1290,1293,1295],{"class":222,"line":285},[220,1289,272],{"class":271},[220,1291,1292],{"class":275}," openpyxl.drawing.text ",[220,1294,279],{"class":271},[220,1296,1297],{"class":275}," CharacterProperties, Paragraph, ParagraphProperties, RichTextProperties\n",[220,1299,1300],{"class":222,"line":298},[220,1301,302],{"emptyLinePlaceholder":301},[220,1303,1304,1306,1309,1312,1314,1317],{"class":222,"line":305},[220,1305,466],{"class":271},[220,1307,1308],{"class":469}," rotated_labels",[220,1310,1311],{"class":275},"(angle_degrees, size_pt",[220,1313,311],{"class":271},[220,1315,1316],{"class":362},"8",[220,1318,377],{"class":275},[220,1320,1321,1323,1326,1329,1331,1334,1337,1339,1341,1344,1346,1349,1351,1354,1356,1359],{"class":222,"line":327},[220,1322,627],{"class":271},[220,1324,1325],{"class":275}," RichText(",[220,1327,1328],{"class":504},"bodyPr",[220,1330,311],{"class":271},[220,1332,1333],{"class":275},"RichTextProperties(",[220,1335,1336],{"class":504},"rot",[220,1338,311],{"class":271},[220,1340,410],{"class":362},[220,1342,1343],{"class":275},"(angle_degrees ",[220,1345,445],{"class":271},[220,1347,1348],{"class":362}," 60000",[220,1350,531],{"class":275},[220,1352,1353],{"class":504},"vert",[220,1355,311],{"class":271},[220,1357,1358],{"class":230},"\"horz\"",[220,1360,1361],{"class":275},"),\n",[220,1363,1364,1367,1369,1372,1375,1377,1380,1383,1385,1388,1391,1393,1396,1398,1401],{"class":222,"line":350},[220,1365,1366],{"class":504},"                    p",[220,1368,311],{"class":271},[220,1370,1371],{"class":275},"[Paragraph(",[220,1373,1374],{"class":504},"pPr",[220,1376,311],{"class":271},[220,1378,1379],{"class":275},"ParagraphProperties(",[220,1381,1382],{"class":504},"defRPr",[220,1384,311],{"class":271},[220,1386,1387],{"class":275},"CharacterProperties(",[220,1389,1390],{"class":504},"sz",[220,1392,311],{"class":271},[220,1394,1395],{"class":275},"size_pt ",[220,1397,445],{"class":271},[220,1399,1400],{"class":362}," 100",[220,1402,1403],{"class":275},")),\n",[220,1405,1406,1409,1411],{"class":222,"line":380},[220,1407,1408],{"class":504},"                                 endParaRPr",[220,1410,311],{"class":271},[220,1412,1413],{"class":275},"CharacterProperties())])\n",[220,1415,1416],{"class":222,"line":458},[220,1417,302],{"emptyLinePlaceholder":301},[220,1419,1420,1423,1425,1427,1430],{"class":222,"line":463},[220,1421,1422],{"class":275},"narrow ",[220,1424,311],{"class":271},[220,1426,646],{"class":275},[220,1428,1429],{"class":230},"\"Rotated category labels\"",[220,1431,542],{"class":275},[220,1433,1434,1437,1439],{"class":222,"line":476},[220,1435,1436],{"class":275},"narrow.width ",[220,1438,311],{"class":271},[220,1440,1441],{"class":362}," 12\n",[220,1443,1444,1447,1449,1452,1454,1457],{"class":222,"line":487},[220,1445,1446],{"class":275},"narrow.x_axis.txPr ",[220,1448,311],{"class":271},[220,1450,1451],{"class":275}," rotated_labels(",[220,1453,435],{"class":271},[220,1455,1456],{"class":362},"45",[220,1458,542],{"class":275},[220,1460,1461,1464,1467],{"class":222,"line":498},[220,1462,1463],{"class":275},"ws.add_chart(narrow, ",[220,1465,1466],{"class":230},"\"E74\"",[220,1468,542],{"class":275},[220,1470,1471],{"class":222,"line":545},[220,1472,302],{"emptyLinePlaceholder":301},[220,1474,1475,1478,1480,1482,1485],{"class":222,"line":576},[220,1476,1477],{"class":275},"sparse ",[220,1479,311],{"class":271},[220,1481,646],{"class":275},[220,1483,1484],{"class":230},"\"Every second label\"",[220,1486,542],{"class":275},[220,1488,1489,1492,1494],{"class":222,"line":587},[220,1490,1491],{"class":275},"sparse.width ",[220,1493,311],{"class":271},[220,1495,1441],{"class":362},[220,1497,1498,1501,1503],{"class":222,"line":597},[220,1499,1500],{"class":275},"sparse.x_axis.tickLblSkip ",[220,1502,311],{"class":271},[220,1504,1505],{"class":362}," 2\n",[220,1507,1508,1511,1514],{"class":222,"line":608},[220,1509,1510],{"class":275},"ws.add_chart(sparse, ",[220,1512,1513],{"class":230},"\"E92\"",[220,1515,542],{"class":275},[220,1517,1518,1520],{"class":222,"line":624},[220,1519,679],{"class":362},[220,1521,1522],{"class":275},"(narrow.x_axis.txPr.bodyPr.rot, sparse.x_axis.tickLblSkip)\n",[10,1524,1525,1526,1529,1530,1533,1534,1537],{},"A negative angle tilts the labels upward to the right, the usual choice. The font size in ",[14,1527,1528],{},"CharacterProperties"," is in hundredths of a point, so ",[14,1531,1532],{},"800"," is 8 pt. ",[14,1535,1536],{},"tickLblSkip = 2"," shows every second label while keeping every data point.",[10,1539,1540,1541,1544,1545,1548],{},"For a column chart with negative values, the category labels sit on the zero line by default — in the middle of the negative bars. Move them to the bottom with ",[14,1542,1543],{},"tickLblPos = \"low\"",", and set each series' ",[14,1546,1547],{},"invertIfNegative"," explicitly, because openpyxl leaves it unset and some applications then draw negative bars incorrectly (LibreOffice draws them upward, as if they were positive):",[211,1550,1552],{"className":262,"code":1551,"language":264,"meta":216,"style":216},"margin = BarChart()\nmargin.title = \"Weekly margin\"\nmargin.add_data(Reference(ws, min_col=3, min_row=1, max_row=13), titles_from_data=True)\nmargin.set_categories(Reference(ws, min_col=1, min_row=2, max_row=13))\nmargin.x_axis.delete = False\nmargin.y_axis.delete = False\nmargin.x_axis.tickLblPos = \"low\"\nmargin.x_axis.txPr = rotated_labels(-45)\nmargin.y_axis.number_format = '0.0\"%\"'\nmargin.series[0].invertIfNegative = False\nmargin.legend = None\nws.add_chart(margin, \"E110\")\nwb.save(\"chart_axes.xlsx\")\nprint(\"negative weeks:\", sum(1 for r in range(2, 14) if ws.cell(row=r, column=3).value \u003C 0))\n",[14,1553,1554,1564,1574,1611,1640,1649,1658,1668,1683,1693,1707,1716,1726,1736],{"__ignoreMap":216},[220,1555,1556,1559,1561],{"class":222,"line":223},[220,1557,1558],{"class":275},"margin ",[220,1560,311],{"class":271},[220,1562,1563],{"class":275}," BarChart()\n",[220,1565,1566,1569,1571],{"class":222,"line":285},[220,1567,1568],{"class":275},"margin.title ",[220,1570,311],{"class":271},[220,1572,1573],{"class":230}," \"Weekly margin\"\n",[220,1575,1576,1579,1581,1583,1585,1587,1589,1591,1593,1595,1597,1599,1601,1603,1605,1607,1609],{"class":222,"line":298},[220,1577,1578],{"class":275},"margin.add_data(Reference(ws, ",[220,1580,505],{"class":504},[220,1582,311],{"class":271},[220,1584,1085],{"class":362},[220,1586,336],{"class":275},[220,1588,515],{"class":504},[220,1590,311],{"class":271},[220,1592,369],{"class":362},[220,1594,336],{"class":275},[220,1596,524],{"class":504},[220,1598,311],{"class":271},[220,1600,374],{"class":362},[220,1602,531],{"class":275},[220,1604,534],{"class":504},[220,1606,311],{"class":271},[220,1608,539],{"class":362},[220,1610,542],{"class":275},[220,1612,1613,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638],{"class":222,"line":305},[220,1614,1615],{"class":275},"margin.set_categories(Reference(ws, ",[220,1617,505],{"class":504},[220,1619,311],{"class":271},[220,1621,369],{"class":362},[220,1623,336],{"class":275},[220,1625,515],{"class":504},[220,1627,311],{"class":271},[220,1629,510],{"class":362},[220,1631,336],{"class":275},[220,1633,524],{"class":504},[220,1635,311],{"class":271},[220,1637,374],{"class":362},[220,1639,573],{"class":275},[220,1641,1642,1645,1647],{"class":222,"line":327},[220,1643,1644],{"class":275},"margin.x_axis.delete ",[220,1646,311],{"class":271},[220,1648,584],{"class":362},[220,1650,1651,1654,1656],{"class":222,"line":350},[220,1652,1653],{"class":275},"margin.y_axis.delete ",[220,1655,311],{"class":271},[220,1657,584],{"class":362},[220,1659,1660,1663,1665],{"class":222,"line":380},[220,1661,1662],{"class":275},"margin.x_axis.tickLblPos ",[220,1664,311],{"class":271},[220,1666,1667],{"class":230}," \"low\"\n",[220,1669,1670,1673,1675,1677,1679,1681],{"class":222,"line":458},[220,1671,1672],{"class":275},"margin.x_axis.txPr ",[220,1674,311],{"class":271},[220,1676,1451],{"class":275},[220,1678,435],{"class":271},[220,1680,1456],{"class":362},[220,1682,542],{"class":275},[220,1684,1685,1688,1690],{"class":222,"line":463},[220,1686,1687],{"class":275},"margin.y_axis.number_format ",[220,1689,311],{"class":271},[220,1691,1692],{"class":230}," '0.0\"%\"'\n",[220,1694,1695,1698,1700,1703,1705],{"class":222,"line":476},[220,1696,1697],{"class":275},"margin.series[",[220,1699,59],{"class":362},[220,1701,1702],{"class":275},"].invertIfNegative ",[220,1704,311],{"class":271},[220,1706,584],{"class":362},[220,1708,1709,1712,1714],{"class":222,"line":487},[220,1710,1711],{"class":275},"margin.legend ",[220,1713,311],{"class":271},[220,1715,605],{"class":362},[220,1717,1718,1721,1724],{"class":222,"line":498},[220,1719,1720],{"class":275},"ws.add_chart(margin, ",[220,1722,1723],{"class":230},"\"E110\"",[220,1725,542],{"class":275},[220,1727,1728,1731,1734],{"class":222,"line":545},[220,1729,1730],{"class":275},"wb.save(",[220,1732,1733],{"class":230},"\"chart_axes.xlsx\"",[220,1735,542],{"class":275},[220,1737,1738,1740,1742,1745,1747,1750,1752,1754,1757,1759,1761,1763,1765,1767,1769,1771,1773,1776,1778,1780,1782,1784,1786,1788,1790,1792,1795,1798],{"class":222,"line":576},[220,1739,679],{"class":362},[220,1741,366],{"class":275},[220,1743,1744],{"class":230},"\"negative weeks:\"",[220,1746,336],{"class":275},[220,1748,1749],{"class":362},"sum",[220,1751,366],{"class":275},[220,1753,369],{"class":362},[220,1755,1756],{"class":271}," for",[220,1758,1093],{"class":275},[220,1760,359],{"class":271},[220,1762,363],{"class":362},[220,1764,366],{"class":275},[220,1766,510],{"class":362},[220,1768,336],{"class":275},[220,1770,1159],{"class":362},[220,1772,1088],{"class":275},[220,1774,1775],{"class":271},"if",[220,1777,1066],{"class":275},[220,1779,1037],{"class":504},[220,1781,311],{"class":271},[220,1783,1073],{"class":275},[220,1785,1053],{"class":504},[220,1787,311],{"class":271},[220,1789,1085],{"class":362},[220,1791,1060],{"class":275},[220,1793,1794],{"class":271},"\u003C",[220,1796,1797],{"class":362}," 0",[220,1799,573],{"class":275},[10,1801,1802,1803,1806,1807,1810],{},"The first four weeks are negative, and their bars now hang below the zero line with the week labels clear underneath the plot. The format ",[14,1804,1805],{},"0.0\"%\""," appends a percent sign to numbers that are already percentages; use ",[14,1808,1809],{},"0.0%"," instead when the data holds fractions such as 0.05.",[37,1812,46,1818,46,1821,46,1824,46,1828,46,1834,46,1840,46,1846,46,1852,46,1857,46,1860,46,1864,46,1866,46,1869,46,1872,46,1876,46,1878,46,1881,46,1884,46,1888,46,1890,46,1893,46,1896,46,1900,46,1903,46,1908,46,1910,46,1914,46,1917,46,1921,46,1924,46,1928,46,1931,46,1935,46,1938,46,1942,46,1945,46,1949,46,1952,46,1956,46,1959,46,1963,46,1966,46,1970,46,1973,46,1977,46,1980,46,1984,46,1987,46,1991,46,1994,46,1998,46,2001],{"viewBox":1813,"role":40,"ariaLabelledBy":1814,"xmlns":44,"style":1817},"4 0 737 532",[1815,1816],"ax-margin-t","ax-margin-d","width:100%;max-width:737px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[48,1819,1820],{"id":1815},"Weekly margin, with negative weeks below the axis",[52,1822,1823],{"id":1816},"Bar chart of the weekly margin percentage for twelve weeks, from minus 3.1 in week 1 rising by 0.9 each week to 6.8 in week 12. The first four weeks are negative.",[56,1825],{"x":58,"y":59,"width":1826,"height":1827,"fill":62},"737","532",[79,1829,1833],{"x":1830,"y":1831,"style":1832},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Margin % by week",[56,1835],{"x":65,"y":1836,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"48.0","59.3","28.0","7",[79,1841,1845],{"x":1842,"y":1843,"style":1844},"49.7","66.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","W01",[56,1847],{"x":1848,"y":1836,"width":1849,"height":1838,"rx":1839,"fill":1850,"stroke":1195,"style":1851},"89.3","6.0","#fee8f2","stroke-width:1.5px",[79,1853,1856],{"x":1854,"y":1843,"style":1855},"103.3","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:start","-3.1",[56,1858],{"x":65,"y":1859,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"88.0",[79,1861,1863],{"x":1842,"y":1862,"style":1844},"106.5","W02",[56,1865],{"x":1848,"y":1859,"width":1849,"height":1838,"rx":1839,"fill":1850,"stroke":1195,"style":1851},[79,1867,1868],{"x":1854,"y":1862,"style":1855},"-2.2",[56,1870],{"x":65,"y":1871,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"128.0",[79,1873,1875],{"x":1842,"y":1874,"style":1844},"146.5","W03",[56,1877],{"x":1848,"y":1871,"width":1849,"height":1838,"rx":1839,"fill":1850,"stroke":1195,"style":1851},[79,1879,1880],{"x":1854,"y":1874,"style":1855},"-1.3",[56,1882],{"x":65,"y":1883,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"168.0",[79,1885,1887],{"x":1842,"y":1886,"style":1844},"186.5","W04",[56,1889],{"x":1848,"y":1883,"width":1849,"height":1838,"rx":1839,"fill":1850,"stroke":1195,"style":1851},[79,1891,1892],{"x":1854,"y":1886,"style":1855},"-0.4",[56,1894],{"x":65,"y":1895,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"208.0",[79,1897,1899],{"x":1842,"y":1898,"style":1844},"226.5","W05",[56,1901],{"x":1848,"y":1895,"width":1902,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"44.7",[79,1904,1907],{"x":1905,"y":1898,"style":1906},"142.0","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","0.5",[56,1909],{"x":65,"y":192,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},[79,1911,1913],{"x":1842,"y":1912,"style":1844},"266.5","W06",[56,1915],{"x":1848,"y":192,"width":1916,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"125.1",[79,1918,1920],{"x":1919,"y":1912,"style":1906},"222.4","1.4",[56,1922],{"x":65,"y":1923,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"288.0",[79,1925,1927],{"x":1842,"y":1926,"style":1844},"306.5","W07",[56,1929],{"x":1848,"y":1923,"width":1930,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"205.5",[79,1932,1934],{"x":1933,"y":1926,"style":1906},"302.8","2.3",[56,1936],{"x":65,"y":1937,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"328.0",[79,1939,1941],{"x":1842,"y":1940,"style":1844},"346.5","W08",[56,1943],{"x":1848,"y":1937,"width":1944,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"285.9",[79,1946,1948],{"x":1947,"y":1940,"style":1906},"383.2","3.2",[56,1950],{"x":65,"y":1951,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"368.0",[79,1953,1955],{"x":1842,"y":1954,"style":1844},"386.5","W09",[56,1957],{"x":1848,"y":1951,"width":1958,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"366.3",[79,1960,1962],{"x":1961,"y":1954,"style":1906},"463.7","4.1",[56,1964],{"x":65,"y":1965,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"408.0",[79,1967,1969],{"x":1842,"y":1968,"style":1844},"426.5","W10",[56,1971],{"x":1848,"y":1965,"width":1972,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"446.7",[79,1974,1976],{"x":1975,"y":1968,"style":1906},"544.1","5.0",[56,1978],{"x":65,"y":1979,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"448.0",[79,1981,1983],{"x":1842,"y":1982,"style":1844},"466.5","W11",[56,1985],{"x":1848,"y":1979,"width":1986,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"527.1",[79,1988,1990],{"x":1989,"y":1982,"style":1906},"624.5","5.9",[56,1992],{"x":65,"y":1993,"width":1837,"height":1838,"rx":1839,"fill":96,"stroke":69,"style":70},"488.0",[79,1995,1997],{"x":1842,"y":1996,"style":1844},"506.5","W12",[56,1999],{"x":1848,"y":1993,"width":2000,"height":1838,"rx":1839,"fill":1220,"stroke":1216,"style":1851},"607.5",[79,2002,2004],{"x":2003,"y":1996,"style":1906},"704.9","6.8",[206,2006,2008],{"id":2007},"reversing-and-crossing","Reversing and crossing",[10,2010,2011,2014,2015,2018,2019,2023,2024,949],{},[14,2012,2013],{},"scaling.orientation = \"maxMin\""," reverses an axis — used to put the first category at the top of a horizontal bar chart — and ",[14,2016,2017],{},"crosses = \"max\""," moves the other axis to the far end, as in ",[31,2020,2022],{"href":2021},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-gantt-chart-in-excel-with-python\u002F","Create a Gantt Chart in Excel with Python",". A second value axis for a series on a different scale is covered in ",[31,2025,2027],{"href":2026},"\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",[206,2029,2031],{"id":2030},"common-pitfalls","Common pitfalls",[2033,2034,2035,2046,2052,2058,2071,2080],"ul",{},[2036,2037,2038,2042,2043,2045],"li",{},[2039,2040,2041],"strong",{},"Axes missing in Excel."," Set ",[14,2044,688],{}," on both axes of every chart.",[2036,2047,2048,2051],{},[2039,2049,2050],{},"Checking only in LibreOffice."," It shows axes and draws some defaults differently; open the file in Excel before relying on it.",[2036,2053,2054,2057],{},[2039,2055,2056],{},"Truncated value axes."," Starting a bar chart's axis above zero exaggerates differences.",[2036,2059,2060,2063,2064,2067,2068,2070],{},[2039,2061,2062],{},"Wrong percent format."," ",[14,2065,2066],{},"0%"," multiplies by 100; use ",[14,2069,1805],{}," for values already in percent.",[2036,2072,2073,2063,2076,2079],{},[2039,2074,2075],{},"Font sizes in points.",[14,2077,2078],{},"CharacterProperties(sz=...)"," is in hundredths of a point.",[2036,2081,2082,2042,2085,2088,2089,949],{},[2039,2083,2084],{},"Negative bars.",[14,2086,2087],{},"invertIfNegative = False"," and move labels with ",[14,2090,1543],{},[206,2092,2094],{"id":2093},"performance-notes","Performance notes",[10,2096,2097],{},"Axis settings are a few XML attributes on the chart and have no effect on file size or speed. Build charts through one helper function so the settings every chart needs — visible axes, sizes, fonts — are applied consistently.",[206,2099,2101],{"id":2100},"conclusion","Conclusion",[10,2103,2104,2105,2107,2108,336,2111,25,2114,2117,2118,2120,2121,25,2123,2126,2127,2129,2130,2132,2133,2136,2137,2139,2140,2142],{},"Set ",[14,2106,688],{}," on both axes of every openpyxl chart so Excel shows them. Control the range with ",[14,2109,2110],{},"scaling.min",[14,2112,2113],{},"scaling.max",[14,2115,2116],{},"majorUnit",", the labels with ",[14,2119,145],{},", and gridlines with ",[14,2122,163],{},[14,2124,2125],{},"minorGridlines",". Use ",[14,2128,181],{}," for fast growth, ",[14,2131,199],{}," rotation or ",[14,2134,2135],{},"tickLblSkip"," for crowded categories, and ",[14,2138,1543],{}," with ",[14,2141,2087],{}," for charts with negative values.",[206,2144,2146],{"id":2145},"frequently-asked-questions","Frequently asked questions",[10,2148,2149,2152],{},[2039,2150,2151],{},"Why does my openpyxl chart have no axes in Excel?","\nopenpyxl 3.1 does not write the flag that marks an axis as visible, and Excel hides an axis without it. Set chart.x_axis.delete = False and chart.y_axis.delete = False. LibreOffice shows the axes anyway, which is why the problem often goes unnoticed until the file is opened in Excel.",[10,2154,2155,2158],{},[2039,2156,2157],{},"How do I set the minimum and maximum of a chart axis with openpyxl?","\nUse the axis's scaling: chart.y_axis.scaling.min = 0 and chart.y_axis.scaling.max = 100. Set chart.y_axis.majorUnit to control the step between labels and gridlines.",[10,2160,2161,2164],{},[2039,2162,2163],{},"How do I format axis numbers as thousands with openpyxl?","\nSet chart.y_axis.number_format = '#,##0,\"k\"'. Each comma after the last digit placeholder divides the displayed value by 1,000, and the quoted text is added as a suffix, so 45000 shows as 45k.",[10,2166,2167,2170],{},[2039,2168,2169],{},"How do I rotate the category labels of a chart in openpyxl?","\nGive the category axis a txPr text body whose RichTextProperties have a rot value. The angle is in 60,000ths of a degree, so rot=-2700000 tilts the labels by 45 degrees.",[206,2172,2174],{"id":2173},"related","Related",[2033,2176,2177,2183,2190,2195],{},[2036,2178,2179,2180,2182],{},"Up one level: ",[31,2181,34],{"href":33}," — every chart type.",[2036,2184,2185,2189],{},[31,2186,2188],{"href":2187},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl\u002F","Style Chart Colors and Data Labels with openpyxl"," — the series side of styling.",[2036,2191,2192,2194],{},[31,2193,2027],{"href":2026}," — two value axes.",[2036,2196,2197,2201],{},[31,2198,2200],{"href":2199},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-histogram-in-excel-with-python\u002F","Create a Histogram in Excel with Python"," — an axis for continuous bins.",[2203,2204,2205],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":216,"searchDepth":285,"depth":285,"links":2207},[2208,2209,2210,2211,2212,2213,2214,2215,2216,2217,2218,2219],{"id":208,"depth":285,"text":209},{"id":240,"depth":285,"text":241},{"id":692,"depth":285,"text":693},{"id":794,"depth":285,"text":795},{"id":952,"depth":285,"text":953},{"id":1264,"depth":285,"text":1265},{"id":2007,"depth":285,"text":2008},{"id":2030,"depth":285,"text":2031},{"id":2093,"depth":285,"text":2094},{"id":2100,"depth":285,"text":2101},{"id":2145,"depth":285,"text":2146},{"id":2173,"depth":285,"text":2174},"2026-09-25","Make the axes of an openpyxl chart say what you mean: keep them visible in Excel, title them, fix the scale and the step between labels, show thousands as \"k\", switch gridlines on or off, use a logarithmic scale for fast growth, rotate crowded category labels, and move labels clear of negative bars.","md",[2224,2226,2228,2230],{"q":2151,"a":2225},"openpyxl 3.1 does not write the flag that marks an axis as visible, and Excel hides an axis without it. Set chart.x_axis.delete = False and chart.y_axis.delete = False. LibreOffice shows the axes anyway, which is why the problem often goes unnoticed until the file is opened in Excel.",{"q":2157,"a":2227},"Use the axis's scaling: chart.y_axis.scaling.min = 0 and chart.y_axis.scaling.max = 100. Set chart.y_axis.majorUnit to control the step between labels and gridlines.",{"q":2163,"a":2229},"Set chart.y_axis.number_format = '#,##0,\"k\"'. Each comma after the last digit placeholder divides the displayed value by 1,000, and the quoted text is added as a suffix, so 45000 shows as 45k.",{"q":2169,"a":2231},"Give the category axis a txPr text body whose RichTextProperties have a rot value. The angle is in 60,000ths of a degree, so rot=-2700000 tilts the labels by 45 degrees.","format chart axes with openpyxl",null,{"breadcrumb":2235},[2236,2238,2241],{"name":2237,"item":1063},"Home",{"name":2239,"item":2240},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":34,"item":33},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl",{"title":2244,"description":2245},"openpyxl Chart Axes: Scale, Number Format, Gridlines, Labels","Format openpyxl chart axes: delete=False so axes show in Excel, axis titles, scaling min, max and majorUnit, number_format such as #,##0,\"k\", majorGridlines and minorGridlines, logBase, rotated labels with txPr, tickLblSkip, tickLblPos low and invertIfNegative.","format-chart-axes-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl\u002Findex",[2249,2250],"charts-and-images","openpyxl","how-to","AI7mYMAm4b6y7179LHq-Ae-HZo2EkUNrMZDCEudG6GU",[2254,2258],{"title":2255,"path":2256,"stem":2257,"children":-1},"Create One Chart per Group with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-one-chart-per-group-with-openpyxl\u002Findex",{"title":2259,"path":2260,"stem":2261,"children":-1},"Make an Excel Chart Grow with Its Data","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fmake-an-excel-chart-grow-with-its-data","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fmake-an-excel-chart-grow-with-its-data\u002Findex",[2263,2265],{"path":2264,"title":34},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":2266,"title":2239},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2269,2272,2275,2278,2281,2284],{"path":2270,"title":2271,"kind":2251},"\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":2273,"title":2274,"kind":2251},"\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":2276,"title":2277,"kind":2251},"\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":2279,"title":2280,"kind":2251},"\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":2282,"title":2283,"kind":2251},"\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":2285,"title":2286,"kind":2251},"\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",1790972111317]