[{"data":1,"prerenderedAt":2045},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl":2011,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl":2020,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl":2025,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl":2026},{"id":4,"title":5,"body":6,"dateModified":1977,"datePublished":1977,"description":1978,"extension":1979,"faq":1980,"keyword":1989,"kind":1990,"meta":1991,"navigation":221,"path":2000,"seo":2001,"slug":2004,"stem":2005,"topics":2006,"type":2009,"__hash__":2010},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl\u002Findex.md","Use a Date Axis in an Excel Chart with openpyxl",{"type":7,"value":8,"toc":1963},"minimark",[9,24,29,55,69,173,177,180,530,545,549,556,722,740,803,807,889,892,896,915,1108,1115,1119,1122,1418,1430,1558,1562,1569,1771,1813,1817,1820,1824,1877,1881,1898,1902,1908,1914,1920,1926,1930,1959],[10,11,12,13,17,18,23],"p",{},"A line chart of measurements taken on irregular dates tells a lie by default. Eight inspections — three in the first week of January, two in March, two in June, one in September — are drawn as eight evenly spaced points, so a nine-month improvement looks like a steady decline over eight equal steps, and the long gaps between inspection rounds disappear. The fix is a ",[14,15,16],"em",{},"date axis",", which places each point at its actual date. openpyxl charts use a text (category) axis unless told otherwise; switching takes a few properties that are easy to get wrong. This page is part of ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," openpyxl\n",[10,56,57,60,61,64,65,68],{},[37,58,59],{},"measurements.xlsx"," has an ",[37,62,63],{},"Inspections"," sheet with eight quality inspections on irregular dates and a defect rate for each, and a ",[37,66,67],{},"Readings"," sheet with temperature readings at irregular times within one day. Both sheets store real Excel dates, not text — a date axis only works with real date values.",[70,71,79,80,79,84,79,88,79,96,79,106,79,117,79,124,79,130,79,135,79,139,79,143,79,148,79,155,79,160,79,164,79,167,79,170],"svg",{"viewBox":72,"role":73,"ariaLabelledBy":74,"xmlns":77,"style":78},"4 0 752 207","img",[75,76],"da-compare-t","da-compare-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  ",[81,82,83],"title",{"id":75},"The same eight inspections on two axes",[85,86,87],"desc",{"id":76},"Two panels. On a text axis the eight points are evenly spaced, hiding the long gaps between inspection rounds. On a date axis the points bunch in January, March and June and leave the true gaps visible.",[89,90],"rect",{"x":91,"y":92,"width":93,"height":94,"fill":95},"4","0","752","207","#ffffff",[89,97],{"x":98,"y":99,"width":100,"height":101,"rx":102,"fill":103,"stroke":104,"style":105},"20.0","16.0","351.0","175.0","14","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[107,108,110],"g",{"transform":109},"translate(182.5,29.0) scale(1.083)",[111,112],"path",{"d":113,"fill":114,"stroke":115,"style":116},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[118,119,123],"text",{"x":120,"y":121,"style":122},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Text axis (default)",[42,125],{"x1":126,"y1":127,"x2":128,"y2":127,"stroke":104,"style":129},"36.0","91.0","355.0","stroke-width:1px",[118,131,134],{"x":120,"y":132,"style":133},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","one slot per row",[118,136,138],{"x":120,"y":137,"style":133},"137.0","5 Jan and 7 Jan as far apart as 16 Jun and 1 Sep",[118,140,142],{"x":120,"y":141,"style":133},"163.0","improvement looks steady",[89,144],{"x":145,"y":99,"width":100,"height":101,"rx":102,"fill":146,"stroke":147,"style":105},"389.0","#d9f4f1","var(--teal,#0f9488)",[107,149,151],{"transform":150},"translate(551.5,29.0) scale(1.083)",[111,152],{"d":153,"fill":114,"stroke":154,"style":116},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[118,156,159],{"x":157,"y":121,"style":158},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Date axis",[42,161],{"x1":162,"y1":127,"x2":163,"y2":127,"stroke":147,"style":129},"405.0","724.0",[118,165,166],{"x":157,"y":132,"style":133},"points at their real dates",[118,168,169],{"x":157,"y":137,"style":133},"bunches and gaps visible",[118,171,172],{"x":157,"y":141,"style":133},"rate of change is honest",[25,174,176],{"id":175},"step-1-the-default-a-text-axis","Step 1 — The default: a text axis",[10,178,179],{},"For comparison, build the chart the default way:",[30,181,185],{"className":182,"code":183,"language":184,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\nfrom openpyxl.chart import LineChart, Reference\n\nwb = load_workbook(\"measurements.xlsx\")\nws = wb[\"Inspections\"]\n\ndef base_chart(title):\n    ch = LineChart()\n    ch.title = title\n    ch.add_data(Reference(ws, min_col=2, min_row=1, max_row=9), titles_from_data=True)\n    ch.set_categories(Reference(ws, min_col=1, min_row=2, max_row=9))\n    s = ch.series[0]\n    s.smooth = False\n    s.marker.symbol = \"circle\"\n    s.marker.size = 7\n    ch.legend = None\n    ch.width, ch.height = 15, 7\n    return ch\n\ntext_chart = base_chart(\"Defect rate — text axis\")\ntext_chart.x_axis.number_format = \"dd mmm\"\ntext_chart.x_axis.delete = text_chart.y_axis.delete = False\nprint(type(text_chart.x_axis).__name__)\n","python",[37,186,187,203,216,223,241,258,263,276,287,298,348,379,394,405,416,427,438,454,463,468,484,495,510],{"__ignoreMap":35},[40,188,189,193,197,200],{"class":42,"line":43},[40,190,192],{"class":191},"s-kum","from",[40,194,196],{"class":195},"skGVy"," openpyxl ",[40,198,199],{"class":191},"import",[40,201,202],{"class":195}," load_workbook\n",[40,204,206,208,211,213],{"class":42,"line":205},2,[40,207,192],{"class":191},[40,209,210],{"class":195}," openpyxl.chart ",[40,212,199],{"class":191},[40,214,215],{"class":195}," LineChart, Reference\n",[40,217,219],{"class":42,"line":218},3,[40,220,222],{"emptyLinePlaceholder":221},true,"\n",[40,224,226,229,232,235,238],{"class":42,"line":225},4,[40,227,228],{"class":195},"wb ",[40,230,231],{"class":191},"=",[40,233,234],{"class":195}," load_workbook(",[40,236,237],{"class":50},"\"measurements.xlsx\"",[40,239,240],{"class":195},")\n",[40,242,244,247,249,252,255],{"class":42,"line":243},5,[40,245,246],{"class":195},"ws ",[40,248,231],{"class":191},[40,250,251],{"class":195}," wb[",[40,253,254],{"class":50},"\"Inspections\"",[40,256,257],{"class":195},"]\n",[40,259,261],{"class":42,"line":260},6,[40,262,222],{"emptyLinePlaceholder":221},[40,264,266,269,273],{"class":42,"line":265},7,[40,267,268],{"class":191},"def",[40,270,272],{"class":271},"s_Opv"," base_chart",[40,274,275],{"class":195},"(title):\n",[40,277,279,282,284],{"class":42,"line":278},8,[40,280,281],{"class":195},"    ch ",[40,283,231],{"class":191},[40,285,286],{"class":195}," LineChart()\n",[40,288,290,293,295],{"class":42,"line":289},9,[40,291,292],{"class":195},"    ch.title ",[40,294,231],{"class":191},[40,296,297],{"class":195}," title\n",[40,299,301,304,308,310,314,317,320,322,325,327,330,332,335,338,341,343,346],{"class":42,"line":300},10,[40,302,303],{"class":195},"    ch.add_data(Reference(ws, ",[40,305,307],{"class":306},"sa561","min_col",[40,309,231],{"class":191},[40,311,313],{"class":312},"sP0c6","2",[40,315,316],{"class":195},", ",[40,318,319],{"class":306},"min_row",[40,321,231],{"class":191},[40,323,324],{"class":312},"1",[40,326,316],{"class":195},[40,328,329],{"class":306},"max_row",[40,331,231],{"class":191},[40,333,334],{"class":312},"9",[40,336,337],{"class":195},"), ",[40,339,340],{"class":306},"titles_from_data",[40,342,231],{"class":191},[40,344,345],{"class":312},"True",[40,347,240],{"class":195},[40,349,351,354,356,358,360,362,364,366,368,370,372,374,376],{"class":42,"line":350},11,[40,352,353],{"class":195},"    ch.set_categories(Reference(ws, ",[40,355,307],{"class":306},[40,357,231],{"class":191},[40,359,324],{"class":312},[40,361,316],{"class":195},[40,363,319],{"class":306},[40,365,231],{"class":191},[40,367,313],{"class":312},[40,369,316],{"class":195},[40,371,329],{"class":306},[40,373,231],{"class":191},[40,375,334],{"class":312},[40,377,378],{"class":195},"))\n",[40,380,382,385,387,390,392],{"class":42,"line":381},12,[40,383,384],{"class":195},"    s ",[40,386,231],{"class":191},[40,388,389],{"class":195}," ch.series[",[40,391,92],{"class":312},[40,393,257],{"class":195},[40,395,397,400,402],{"class":42,"line":396},13,[40,398,399],{"class":195},"    s.smooth ",[40,401,231],{"class":191},[40,403,404],{"class":312}," False\n",[40,406,408,411,413],{"class":42,"line":407},14,[40,409,410],{"class":195},"    s.marker.symbol ",[40,412,231],{"class":191},[40,414,415],{"class":50}," \"circle\"\n",[40,417,419,422,424],{"class":42,"line":418},15,[40,420,421],{"class":195},"    s.marker.size ",[40,423,231],{"class":191},[40,425,426],{"class":312}," 7\n",[40,428,430,433,435],{"class":42,"line":429},16,[40,431,432],{"class":195},"    ch.legend ",[40,434,231],{"class":191},[40,436,437],{"class":312}," None\n",[40,439,441,444,446,449,451],{"class":42,"line":440},17,[40,442,443],{"class":195},"    ch.width, ch.height ",[40,445,231],{"class":191},[40,447,448],{"class":312}," 15",[40,450,316],{"class":195},[40,452,453],{"class":312},"7\n",[40,455,457,460],{"class":42,"line":456},18,[40,458,459],{"class":191},"    return",[40,461,462],{"class":195}," ch\n",[40,464,466],{"class":42,"line":465},19,[40,467,222],{"emptyLinePlaceholder":221},[40,469,471,474,476,479,482],{"class":42,"line":470},20,[40,472,473],{"class":195},"text_chart ",[40,475,231],{"class":191},[40,477,478],{"class":195}," base_chart(",[40,480,481],{"class":50},"\"Defect rate — text axis\"",[40,483,240],{"class":195},[40,485,487,490,492],{"class":42,"line":486},21,[40,488,489],{"class":195},"text_chart.x_axis.number_format ",[40,491,231],{"class":191},[40,493,494],{"class":50}," \"dd mmm\"\n",[40,496,498,501,503,506,508],{"class":42,"line":497},22,[40,499,500],{"class":195},"text_chart.x_axis.delete ",[40,502,231],{"class":191},[40,504,505],{"class":195}," text_chart.y_axis.delete ",[40,507,231],{"class":191},[40,509,404],{"class":312},[40,511,513,516,519,522,525,528],{"class":42,"line":512},23,[40,514,515],{"class":312},"print",[40,517,518],{"class":195},"(",[40,520,521],{"class":312},"type",[40,523,524],{"class":195},"(text_chart.x_axis).",[40,526,527],{"class":312},"__name__",[40,529,240],{"class":195},[10,531,532,533,536,537,540,541,544],{},"The default ",[37,534,535],{},"x_axis"," is a ",[37,538,539],{},"TextAxis",": each category gets one equal slot in row order, whatever the values are. ",[37,542,543],{},"smooth = False"," matters too — openpyxl writes line series as smoothed curves unless told otherwise, and a smoothed curve through measurements implies values between them that were never measured.",[25,546,548],{"id":547},"step-2-switch-to-a-date-axis","Step 2 — Switch to a date axis",[10,550,551,552,555],{},"Replace the x axis with a ",[37,553,554],{},"DateAxis",", and pair it with the value axis through their axis IDs:",[30,557,559],{"className":182,"code":558,"language":184,"meta":35,"style":35},"from openpyxl.chart.axis import DateAxis\n\ndate_chart = base_chart(\"Defect rate — date axis\")\ndate_chart.y_axis.crossAx = 500\ndate_chart.x_axis = DateAxis(crossAx=100)\ndate_chart.x_axis.number_format = \"mmm\"\ndate_chart.x_axis.majorTimeUnit = \"months\"\ndate_chart.x_axis.majorUnit = 1\ndate_chart.x_axis.delete = date_chart.y_axis.delete = False\n\nws.column_dimensions[\"A\"].width = 13\nws.add_chart(text_chart, \"A12\")\nws.add_chart(date_chart, \"A27\")\nwb.save(\"measurements_charts.xlsx\")\nprint(date_chart.x_axis.axId, date_chart.y_axis.axId, date_chart.y_axis.crossAx)\n",[37,560,561,573,577,591,601,621,631,641,651,665,669,685,695,705,715],{"__ignoreMap":35},[40,562,563,565,568,570],{"class":42,"line":43},[40,564,192],{"class":191},[40,566,567],{"class":195}," openpyxl.chart.axis ",[40,569,199],{"class":191},[40,571,572],{"class":195}," DateAxis\n",[40,574,575],{"class":42,"line":205},[40,576,222],{"emptyLinePlaceholder":221},[40,578,579,582,584,586,589],{"class":42,"line":218},[40,580,581],{"class":195},"date_chart ",[40,583,231],{"class":191},[40,585,478],{"class":195},[40,587,588],{"class":50},"\"Defect rate — date axis\"",[40,590,240],{"class":195},[40,592,593,596,598],{"class":42,"line":225},[40,594,595],{"class":195},"date_chart.y_axis.crossAx ",[40,597,231],{"class":191},[40,599,600],{"class":312}," 500\n",[40,602,603,606,608,611,614,616,619],{"class":42,"line":243},[40,604,605],{"class":195},"date_chart.x_axis ",[40,607,231],{"class":191},[40,609,610],{"class":195}," DateAxis(",[40,612,613],{"class":306},"crossAx",[40,615,231],{"class":191},[40,617,618],{"class":312},"100",[40,620,240],{"class":195},[40,622,623,626,628],{"class":42,"line":260},[40,624,625],{"class":195},"date_chart.x_axis.number_format ",[40,627,231],{"class":191},[40,629,630],{"class":50}," \"mmm\"\n",[40,632,633,636,638],{"class":42,"line":265},[40,634,635],{"class":195},"date_chart.x_axis.majorTimeUnit ",[40,637,231],{"class":191},[40,639,640],{"class":50}," \"months\"\n",[40,642,643,646,648],{"class":42,"line":278},[40,644,645],{"class":195},"date_chart.x_axis.majorUnit ",[40,647,231],{"class":191},[40,649,650],{"class":312}," 1\n",[40,652,653,656,658,661,663],{"class":42,"line":289},[40,654,655],{"class":195},"date_chart.x_axis.delete ",[40,657,231],{"class":191},[40,659,660],{"class":195}," date_chart.y_axis.delete ",[40,662,231],{"class":191},[40,664,404],{"class":312},[40,666,667],{"class":42,"line":300},[40,668,222],{"emptyLinePlaceholder":221},[40,670,671,674,677,680,682],{"class":42,"line":350},[40,672,673],{"class":195},"ws.column_dimensions[",[40,675,676],{"class":50},"\"A\"",[40,678,679],{"class":195},"].width ",[40,681,231],{"class":191},[40,683,684],{"class":312}," 13\n",[40,686,687,690,693],{"class":42,"line":381},[40,688,689],{"class":195},"ws.add_chart(text_chart, ",[40,691,692],{"class":50},"\"A12\"",[40,694,240],{"class":195},[40,696,697,700,703],{"class":42,"line":396},[40,698,699],{"class":195},"ws.add_chart(date_chart, ",[40,701,702],{"class":50},"\"A27\"",[40,704,240],{"class":195},[40,706,707,710,713],{"class":42,"line":407},[40,708,709],{"class":195},"wb.save(",[40,711,712],{"class":50},"\"measurements_charts.xlsx\"",[40,714,240],{"class":195},[40,716,717,719],{"class":42,"line":418},[40,718,515],{"class":312},[40,720,721],{"class":195},"(date_chart.x_axis.axId, date_chart.y_axis.axId, date_chart.y_axis.crossAx)\n",[10,723,724,725,727,728,731,732,735,736,739],{},"Every chart axis has an ID, and each axis names the axis it crosses. openpyxl's default value axis has ID 100 and a ",[37,726,554],{}," has ID 500, so the value axis must be told to cross 500 and the date axis to cross 100 — skip either line and Excel cannot pair them, which shows up as a chart with a missing axis or a repair prompt. ",[37,729,730],{},"majorTimeUnit"," and ",[37,733,734],{},"majorUnit"," together set a tick every month; with ",[37,737,738],{},"number_format = \"mmm\""," each tick is labelled Jan, Feb, Mar.",[70,741,79,747,79,750,79,753,79,758,79,766,79,774,79,780,79,785,79,788,79,794,79,799],{"viewBox":742,"role":73,"ariaLabelledBy":743,"xmlns":77,"style":746},"64 0 632 228",[744,745],"da-ids-t","da-ids-d","width:100%;max-width:632px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[81,748,749],{"id":744},"How the two axes find each other",[85,751,752],{"id":745},"Two layers. The value axis has ID 100 and crosses axis 500; the date axis has ID 500 and crosses axis 100. Setting both crossAx values pairs them.",[89,754],{"x":755,"y":92,"width":756,"height":757,"fill":95},"64","632","228",[89,759],{"x":760,"y":99,"width":761,"height":762,"rx":763,"fill":764,"stroke":765,"style":105},"80.0","600.0","93.0","12","#f0f4ff","var(--brand,#5b5cf0)",[107,767,769],{"transform":768},"translate(368.0,29.0) scale(1.000)",[111,770],{"d":771,"fill":114,"stroke":772,"style":773},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[118,775,779],{"x":776,"y":777,"style":778},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Value axis — axId 100",[118,781,784],{"x":776,"y":782,"style":783},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","y_axis.crossAx = 500",[89,786],{"x":760,"y":787,"width":761,"height":762,"rx":763,"fill":146,"stroke":147,"style":105},"119.0",[107,789,791],{"transform":790},"translate(368.0,132.0) scale(1.000)",[111,792],{"d":793,"fill":114,"stroke":154,"style":773},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4",[118,795,798],{"x":776,"y":796,"style":797},"178.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Date axis — axId 500",[118,800,802],{"x":776,"y":801,"style":783},"198.0","DateAxis(crossAx=100)",[25,804,806],{"id":805},"step-3-render-and-compare","Step 3 — Render and compare",[30,808,810],{"className":32,"code":809,"language":34,"meta":35,"style":35},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to pdf measurements_charts.xlsx > \u002Fdev\u002Fnull 2>&1\npdftoppm -png -r 60 -f 1 -l 1 measurements_charts.pdf preview && ls preview*\n",[37,811,812,847],{"__ignoreMap":35},[40,813,814,817,820,823,826,829,832,835,838,841,844],{"class":42,"line":43},[40,815,816],{"class":46},"soffice",[40,818,819],{"class":312}," -env:UserInstallation=file:\u002F\u002F",[40,821,822],{"class":195},"$PWD",[40,824,825],{"class":312},"\u002Flo_profile",[40,827,828],{"class":312}," --headless",[40,830,831],{"class":312}," --convert-to",[40,833,834],{"class":50}," pdf",[40,836,837],{"class":50}," measurements_charts.xlsx",[40,839,840],{"class":191}," >",[40,842,843],{"class":50}," \u002Fdev\u002Fnull",[40,845,846],{"class":191}," 2>&1\n",[40,848,849,852,855,858,861,864,867,870,872,875,878,881,884,886],{"class":42,"line":205},[40,850,851],{"class":46},"pdftoppm",[40,853,854],{"class":312}," -png",[40,856,857],{"class":312}," -r",[40,859,860],{"class":312}," 60",[40,862,863],{"class":312}," -f",[40,865,866],{"class":312}," 1",[40,868,869],{"class":312}," -l",[40,871,866],{"class":312},[40,873,874],{"class":50}," measurements_charts.pdf",[40,876,877],{"class":50}," preview",[40,879,880],{"class":195}," && ",[40,882,883],{"class":46},"ls",[40,885,877],{"class":50},[40,887,888],{"class":312},"*\n",[10,890,891],{},"On the text axis the defect rate falls in eight even steps. On the date axis the January points bunch together at the left edge, the rate drops sharply between January and March, and the last point sits alone in September after a two-and-a-half-month gap. That is the real story — most of the improvement happened in the first two months — and only the date axis tells it.",[25,893,895],{"id":894},"step-4-choose-tick-units-and-label-formats","Step 4 — Choose tick units and label formats",[10,897,898,900,901,316,904,907,908,911,912,914],{},[37,899,730],{}," is ",[37,902,903],{},"\"days\"",[37,905,906],{},"\"months\""," or ",[37,909,910],{},"\"years\"","; ",[37,913,734],{}," is how many of those between ticks. Match them to the span of the data and the label format to the unit:",[30,916,918],{"className":182,"code":917,"language":184,"meta":35,"style":35},"SETTINGS = {\n    \"a few weeks\":  (\"days\", 7, \"dd mmm\"),\n    \"a year\":       (\"months\", 1, \"mmm\"),\n    \"several years\": (\"months\", 3, \"mmm yy\"),\n    \"a decade\":     (\"years\", 1, \"yyyy\"),\n}\nfor span, (unit, step, fmt) in SETTINGS.items():\n    print(f\"{span:14} majorTimeUnit={unit!r:9} majorUnit={step:\u003C2} number_format={fmt!r}\")\n",[37,919,920,931,954,975,997,1018,1023,1040],{"__ignoreMap":35},[40,921,922,925,928],{"class":42,"line":43},[40,923,924],{"class":312},"SETTINGS",[40,926,927],{"class":191}," =",[40,929,930],{"class":195}," {\n",[40,932,933,936,939,941,943,946,948,951],{"class":42,"line":205},[40,934,935],{"class":50},"    \"a few weeks\"",[40,937,938],{"class":195},":  (",[40,940,903],{"class":50},[40,942,316],{"class":195},[40,944,945],{"class":312},"7",[40,947,316],{"class":195},[40,949,950],{"class":50},"\"dd mmm\"",[40,952,953],{"class":195},"),\n",[40,955,956,959,962,964,966,968,970,973],{"class":42,"line":218},[40,957,958],{"class":50},"    \"a year\"",[40,960,961],{"class":195},":       (",[40,963,906],{"class":50},[40,965,316],{"class":195},[40,967,324],{"class":312},[40,969,316],{"class":195},[40,971,972],{"class":50},"\"mmm\"",[40,974,953],{"class":195},[40,976,977,980,983,985,987,990,992,995],{"class":42,"line":225},[40,978,979],{"class":50},"    \"several years\"",[40,981,982],{"class":195},": (",[40,984,906],{"class":50},[40,986,316],{"class":195},[40,988,989],{"class":312},"3",[40,991,316],{"class":195},[40,993,994],{"class":50},"\"mmm yy\"",[40,996,953],{"class":195},[40,998,999,1002,1005,1007,1009,1011,1013,1016],{"class":42,"line":243},[40,1000,1001],{"class":50},"    \"a decade\"",[40,1003,1004],{"class":195},":     (",[40,1006,910],{"class":50},[40,1008,316],{"class":195},[40,1010,324],{"class":312},[40,1012,316],{"class":195},[40,1014,1015],{"class":50},"\"yyyy\"",[40,1017,953],{"class":195},[40,1019,1020],{"class":42,"line":260},[40,1021,1022],{"class":195},"}\n",[40,1024,1025,1028,1031,1034,1037],{"class":42,"line":265},[40,1026,1027],{"class":191},"for",[40,1029,1030],{"class":195}," span, (unit, step, fmt) ",[40,1032,1033],{"class":191},"in",[40,1035,1036],{"class":312}," SETTINGS",[40,1038,1039],{"class":195},".items():\n",[40,1041,1042,1045,1047,1050,1053,1057,1059,1062,1065,1068,1070,1073,1076,1078,1081,1083,1086,1089,1091,1094,1096,1099,1102,1104,1106],{"class":42,"line":278},[40,1043,1044],{"class":312},"    print",[40,1046,518],{"class":195},[40,1048,1049],{"class":191},"f",[40,1051,1052],{"class":50},"\"",[40,1054,1056],{"class":1055},"sSjpA","{",[40,1058,40],{"class":195},[40,1060,1061],{"class":191},":14",[40,1063,1064],{"class":1055},"}",[40,1066,1067],{"class":50}," majorTimeUnit=",[40,1069,1056],{"class":1055},[40,1071,1072],{"class":195},"unit",[40,1074,1075],{"class":191},"!r:9",[40,1077,1064],{"class":1055},[40,1079,1080],{"class":50}," majorUnit=",[40,1082,1056],{"class":1055},[40,1084,1085],{"class":195},"step",[40,1087,1088],{"class":191},":\u003C2",[40,1090,1064],{"class":1055},[40,1092,1093],{"class":50}," number_format=",[40,1095,1056],{"class":1055},[40,1097,1098],{"class":195},"fmt",[40,1100,1101],{"class":191},"!r",[40,1103,1064],{"class":1055},[40,1105,1052],{"class":50},[40,1107,240],{"class":195},[10,1109,1110,1111,1114],{},"A tick every seven days on a few weeks of data lands on the same weekday each time, which reads naturally. Labels that repeat — ",[37,1112,1113],{},"Jan"," on a chart spanning three Januaries — are the usual sign that the format is missing a year. Too many ticks is the other common problem: a daily tick on a year of data prints 365 overlapping labels, which Excel thins out unpredictably. Choose a unit that gives somewhere between six and twelve labels across the chart.",[25,1116,1118],{"id":1117},"step-5-times-within-a-day-need-a-scatter-chart","Step 5 — Times within a day need a scatter chart",[10,1120,1121],{},"A date axis works in whole days: its base unit is at most a day, so readings at 06:00, 09:00 and 15:00 on the same date are all placed at that date. For timestamps within a day, use a scatter chart, whose x axis is an ordinary number axis — and Excel times are just fractions of a day:",[30,1123,1125],{"className":182,"code":1124,"language":184,"meta":35,"style":35},"from openpyxl.chart import ScatterChart, Series\n\nrs = wb[\"Readings\"]\nscatter = ScatterChart()\nscatter.title = \"Temperature on 28 September\"\nxs = Reference(rs, min_col=1, min_row=2, max_row=rs.max_row)\nys = Reference(rs, min_col=2, min_row=1, max_row=rs.max_row)\nseries = Series(ys, xs, title_from_data=True)\nseries.marker.symbol = \"circle\"\nseries.smooth = False\nscatter.series.append(series)\nscatter.style = 13\nscatter.x_axis.number_format = \"hh:mm\"\nscatter.x_axis.scaling.min = rs[\"A2\"].value.toordinal() - 693594      # Excel serial of midnight\nscatter.x_axis.scaling.max = scatter.x_axis.scaling.min + 1\nscatter.x_axis.majorUnit = 3 \u002F 24\nscatter.x_axis.delete = scatter.y_axis.delete = False\nscatter.legend = None\nscatter.width, scatter.height = 15, 7\nrs.add_chart(scatter, \"A12\")\nwb.save(\"measurements_charts.xlsx\")\nprint(scatter.x_axis.scaling.min, scatter.x_axis.majorUnit)\n",[37,1126,1127,1138,1142,1156,1166,1176,1209,1240,1259,1268,1277,1282,1291,1301,1327,1342,1358,1372,1381,1394,1403,1411],{"__ignoreMap":35},[40,1128,1129,1131,1133,1135],{"class":42,"line":43},[40,1130,192],{"class":191},[40,1132,210],{"class":195},[40,1134,199],{"class":191},[40,1136,1137],{"class":195}," ScatterChart, Series\n",[40,1139,1140],{"class":42,"line":205},[40,1141,222],{"emptyLinePlaceholder":221},[40,1143,1144,1147,1149,1151,1154],{"class":42,"line":218},[40,1145,1146],{"class":195},"rs ",[40,1148,231],{"class":191},[40,1150,251],{"class":195},[40,1152,1153],{"class":50},"\"Readings\"",[40,1155,257],{"class":195},[40,1157,1158,1161,1163],{"class":42,"line":225},[40,1159,1160],{"class":195},"scatter ",[40,1162,231],{"class":191},[40,1164,1165],{"class":195}," ScatterChart()\n",[40,1167,1168,1171,1173],{"class":42,"line":243},[40,1169,1170],{"class":195},"scatter.title ",[40,1172,231],{"class":191},[40,1174,1175],{"class":50}," \"Temperature on 28 September\"\n",[40,1177,1178,1181,1183,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206],{"class":42,"line":260},[40,1179,1180],{"class":195},"xs ",[40,1182,231],{"class":191},[40,1184,1185],{"class":195}," Reference(rs, ",[40,1187,307],{"class":306},[40,1189,231],{"class":191},[40,1191,324],{"class":312},[40,1193,316],{"class":195},[40,1195,319],{"class":306},[40,1197,231],{"class":191},[40,1199,313],{"class":312},[40,1201,316],{"class":195},[40,1203,329],{"class":306},[40,1205,231],{"class":191},[40,1207,1208],{"class":195},"rs.max_row)\n",[40,1210,1211,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234,1236,1238],{"class":42,"line":265},[40,1212,1213],{"class":195},"ys ",[40,1215,231],{"class":191},[40,1217,1185],{"class":195},[40,1219,307],{"class":306},[40,1221,231],{"class":191},[40,1223,313],{"class":312},[40,1225,316],{"class":195},[40,1227,319],{"class":306},[40,1229,231],{"class":191},[40,1231,324],{"class":312},[40,1233,316],{"class":195},[40,1235,329],{"class":306},[40,1237,231],{"class":191},[40,1239,1208],{"class":195},[40,1241,1242,1245,1247,1250,1253,1255,1257],{"class":42,"line":278},[40,1243,1244],{"class":195},"series ",[40,1246,231],{"class":191},[40,1248,1249],{"class":195}," Series(ys, xs, ",[40,1251,1252],{"class":306},"title_from_data",[40,1254,231],{"class":191},[40,1256,345],{"class":312},[40,1258,240],{"class":195},[40,1260,1261,1264,1266],{"class":42,"line":289},[40,1262,1263],{"class":195},"series.marker.symbol ",[40,1265,231],{"class":191},[40,1267,415],{"class":50},[40,1269,1270,1273,1275],{"class":42,"line":300},[40,1271,1272],{"class":195},"series.smooth ",[40,1274,231],{"class":191},[40,1276,404],{"class":312},[40,1278,1279],{"class":42,"line":350},[40,1280,1281],{"class":195},"scatter.series.append(series)\n",[40,1283,1284,1287,1289],{"class":42,"line":381},[40,1285,1286],{"class":195},"scatter.style ",[40,1288,231],{"class":191},[40,1290,684],{"class":312},[40,1292,1293,1296,1298],{"class":42,"line":396},[40,1294,1295],{"class":195},"scatter.x_axis.number_format ",[40,1297,231],{"class":191},[40,1299,1300],{"class":50}," \"hh:mm\"\n",[40,1302,1303,1306,1308,1311,1314,1317,1320,1323],{"class":42,"line":407},[40,1304,1305],{"class":195},"scatter.x_axis.scaling.min ",[40,1307,231],{"class":191},[40,1309,1310],{"class":195}," rs[",[40,1312,1313],{"class":50},"\"A2\"",[40,1315,1316],{"class":195},"].value.toordinal() ",[40,1318,1319],{"class":191},"-",[40,1321,1322],{"class":312}," 693594",[40,1324,1326],{"class":1325},"s-wDw","      # Excel serial of midnight\n",[40,1328,1329,1332,1334,1337,1340],{"class":42,"line":418},[40,1330,1331],{"class":195},"scatter.x_axis.scaling.max ",[40,1333,231],{"class":191},[40,1335,1336],{"class":195}," scatter.x_axis.scaling.min ",[40,1338,1339],{"class":191},"+",[40,1341,650],{"class":312},[40,1343,1344,1347,1349,1352,1355],{"class":42,"line":429},[40,1345,1346],{"class":195},"scatter.x_axis.majorUnit ",[40,1348,231],{"class":191},[40,1350,1351],{"class":312}," 3",[40,1353,1354],{"class":191}," \u002F",[40,1356,1357],{"class":312}," 24\n",[40,1359,1360,1363,1365,1368,1370],{"class":42,"line":440},[40,1361,1362],{"class":195},"scatter.x_axis.delete ",[40,1364,231],{"class":191},[40,1366,1367],{"class":195}," scatter.y_axis.delete ",[40,1369,231],{"class":191},[40,1371,404],{"class":312},[40,1373,1374,1377,1379],{"class":42,"line":456},[40,1375,1376],{"class":195},"scatter.legend ",[40,1378,231],{"class":191},[40,1380,437],{"class":312},[40,1382,1383,1386,1388,1390,1392],{"class":42,"line":465},[40,1384,1385],{"class":195},"scatter.width, scatter.height ",[40,1387,231],{"class":191},[40,1389,448],{"class":312},[40,1391,316],{"class":195},[40,1393,453],{"class":312},[40,1395,1396,1399,1401],{"class":42,"line":470},[40,1397,1398],{"class":195},"rs.add_chart(scatter, ",[40,1400,692],{"class":50},[40,1402,240],{"class":195},[40,1404,1405,1407,1409],{"class":42,"line":486},[40,1406,709],{"class":195},[40,1408,712],{"class":50},[40,1410,240],{"class":195},[40,1412,1413,1415],{"class":42,"line":497},[40,1414,515],{"class":312},[40,1416,1417],{"class":195},"(scatter.x_axis.scaling.min, scatter.x_axis.majorUnit)\n",[10,1419,1420,1421,1424,1425,1429],{},"The x axis runs from midnight to the next midnight in Excel's serial-number terms — day number 46293 for 28 September 2026 — with a tick every three hours (3\u002F24 of a day), labelled ",[37,1422,1423],{},"hh:mm",". The gap between the 00:00 and 03:00 readings and the cluster at 06:00–09:00 are drawn to scale. ",[19,1426,1428],{"href":1427},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl\u002F","Create a Scatter Chart in Excel with openpyxl"," covers scatter charts in general.",[70,1431,79,1436,79,1439,79,1442,79,1445,79,1449,79,1453,79,1456,79,1460,79,1464,79,1467,79,1472,79,1481,79,1486,79,1491,79,1498,79,1503,79,1506,79,1508,79,1511,79,1515,79,1518,79,1521,79,1526,79,1528,79,1531,79,1533,79,1536,79,1540,79,1543,79,1546,79,1552,79,1555],{"viewBox":1432,"role":73,"ariaLabelledBy":1433,"xmlns":77,"style":78},"4 0 752 272",[1434,1435],"da-choose-t","da-choose-d",[81,1437,1438],{"id":1434},"Which x axis for time data?",[85,1440,1441],{"id":1435},"A decision diagram. Evenly spaced periods or trading days use a text axis; irregular dates use a date axis; timestamps within a day use a scatter chart with a number axis formatted as time.",[89,1443],{"x":91,"y":92,"width":93,"height":1444,"fill":95},"272",[89,1446],{"x":1447,"y":99,"width":1448,"height":777,"rx":763,"fill":764,"stroke":765,"style":105},"240.0","280.0",[107,1450,1451],{"transform":768},[111,1452],{"d":793,"fill":114,"stroke":772,"style":773},[118,1454,1455],{"x":776,"y":777,"style":778},"What are the x values?",[42,1457],{"x1":776,"y1":1458,"x2":776,"y2":1459,"stroke":765,"style":105},"91.5","111.5",[42,1461],{"x1":1462,"y1":1459,"x2":1463,"y2":1459,"stroke":765,"style":105},"134.7","625.3",[42,1465],{"x1":1462,"y1":1459,"x2":1462,"y2":1466,"stroke":765,"style":105},"157.5",[1468,1469],"polygon",{"points":1470,"fill":1471},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[89,1473],{"x":1474,"y":1475,"width":1476,"height":1477,"rx":1478,"fill":95,"stroke":1479,"style":1480},"34.7","121.5","200.0","21.0","10","var(--line,#cdd5e6)","stroke-width:1.2px",[118,1482,1485],{"x":1462,"y":1483,"style":1484},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","regular periods or trading days",[89,1487],{"x":98,"y":1488,"width":1489,"height":762,"rx":763,"fill":1490,"stroke":1479,"style":105},"163.5","229.3","#e7ebef",[107,1492,1494],{"transform":1493},"translate(122.7,176.5) scale(1.000)",[111,1495],{"d":1496,"fill":114,"stroke":1497,"style":773},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--text,#172033)",[118,1499,1502],{"x":1462,"y":1500,"style":1501},"223.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Text axis",[118,1504,134],{"x":1462,"y":1505,"style":783},"242.5",[42,1507],{"x1":776,"y1":1459,"x2":776,"y2":1466,"stroke":765,"style":105},[1468,1509],{"points":1510,"fill":1471},"380.0,161.5 375.0,152.5 385.0,152.5",[89,1512],{"x":1513,"y":1475,"width":1514,"height":1477,"rx":1478,"fill":95,"stroke":1479,"style":1480},"327.7","104.5",[118,1516,1517],{"x":776,"y":1483,"style":1484},"irregular dates",[89,1519],{"x":1520,"y":1488,"width":1489,"height":762,"rx":763,"fill":146,"stroke":147,"style":105},"265.3",[107,1522,1524],{"transform":1523},"translate(368.0,176.5) scale(1.000)",[111,1525],{"d":793,"fill":114,"stroke":154,"style":773},[118,1527,554],{"x":776,"y":1500,"style":797},[118,1529,1530],{"x":776,"y":1505,"style":783},"spaced by calendar date",[42,1532],{"x1":1463,"y1":1459,"x2":1463,"y2":1466,"stroke":765,"style":105},[1468,1534],{"points":1535,"fill":1471},"625.3,161.5 620.3,152.5 630.3,152.5",[89,1537],{"x":1538,"y":1475,"width":1539,"height":1477,"rx":1478,"fill":95,"stroke":1479,"style":1480},"563.1","124.4",[118,1541,1542],{"x":1463,"y":1483,"style":1484},"times within a day",[89,1544],{"x":1545,"y":1488,"width":1489,"height":762,"rx":763,"fill":764,"stroke":765,"style":105},"510.7",[107,1547,1549],{"transform":1548},"translate(613.3,176.5) scale(1.000)",[111,1550],{"d":1551,"fill":114,"stroke":772,"style":773},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2",[118,1553,1554],{"x":1463,"y":1500,"style":778},"ScatterChart",[118,1556,1557],{"x":1463,"y":1505,"style":783},"number axis, hh:mm",[25,1559,1561],{"id":1560},"getting-real-dates-into-the-sheet","Getting real dates into the sheet",[10,1563,1564,1565,1568],{},"Everything above depends on the category cells holding dates, not text that looks like dates. Exports often deliver ",[37,1566,1567],{},"\"05\u002F01\u002F2026\""," as a string; written to Excel unchanged, it stays text, and a date axis has nothing to position. Convert with pandas before writing, stating the format so day and month are not swapped:",[30,1570,1572],{"className":182,"code":1571,"language":184,"meta":35,"style":35},"import pandas as pd\n\nraw = pd.DataFrame({\"Date\": [\"05\u002F01\u002F2026\", \"07\u002F01\u002F2026\", \"02\u002F03\u002F2026\"], \"Defect rate %\": [4.2, 4.0, 2.1]})\nraw[\"Date\"] = pd.to_datetime(raw[\"Date\"], format=\"%d\u002F%m\u002F%Y\")\nwith pd.ExcelWriter(\"from_pandas.xlsx\", engine=\"xlsxwriter\", datetime_format=\"dd mmm yyyy\") as xw:\n    raw.to_excel(xw, index=False)\ncheck = load_workbook(\"from_pandas.xlsx\").active\nprint(type(check[\"A2\"].value).__name__, check[\"A2\"].number_format)\n",[37,1573,1574,1587,1591,1643,1677,1716,1731,1745],{"__ignoreMap":35},[40,1575,1576,1578,1581,1584],{"class":42,"line":43},[40,1577,199],{"class":191},[40,1579,1580],{"class":195}," pandas ",[40,1582,1583],{"class":191},"as",[40,1585,1586],{"class":195}," pd\n",[40,1588,1589],{"class":42,"line":205},[40,1590,222],{"emptyLinePlaceholder":221},[40,1592,1593,1596,1598,1601,1604,1607,1609,1611,1614,1616,1619,1622,1625,1627,1630,1632,1635,1637,1640],{"class":42,"line":218},[40,1594,1595],{"class":195},"raw ",[40,1597,231],{"class":191},[40,1599,1600],{"class":195}," pd.DataFrame({",[40,1602,1603],{"class":50},"\"Date\"",[40,1605,1606],{"class":195},": [",[40,1608,1567],{"class":50},[40,1610,316],{"class":195},[40,1612,1613],{"class":50},"\"07\u002F01\u002F2026\"",[40,1615,316],{"class":195},[40,1617,1618],{"class":50},"\"02\u002F03\u002F2026\"",[40,1620,1621],{"class":195},"], ",[40,1623,1624],{"class":50},"\"Defect rate %\"",[40,1626,1606],{"class":195},[40,1628,1629],{"class":312},"4.2",[40,1631,316],{"class":195},[40,1633,1634],{"class":312},"4.0",[40,1636,316],{"class":195},[40,1638,1639],{"class":312},"2.1",[40,1641,1642],{"class":195},"]})\n",[40,1644,1645,1648,1650,1653,1655,1658,1660,1662,1665,1667,1669,1672,1675],{"class":42,"line":225},[40,1646,1647],{"class":195},"raw[",[40,1649,1603],{"class":50},[40,1651,1652],{"class":195},"] ",[40,1654,231],{"class":191},[40,1656,1657],{"class":195}," pd.to_datetime(raw[",[40,1659,1603],{"class":50},[40,1661,1621],{"class":195},[40,1663,1664],{"class":306},"format",[40,1666,231],{"class":191},[40,1668,1052],{"class":50},[40,1670,1671],{"class":1055},"%d",[40,1673,1674],{"class":50},"\u002F%m\u002F%Y\"",[40,1676,240],{"class":195},[40,1678,1679,1682,1685,1688,1690,1693,1695,1698,1700,1703,1705,1708,1711,1713],{"class":42,"line":243},[40,1680,1681],{"class":191},"with",[40,1683,1684],{"class":195}," pd.ExcelWriter(",[40,1686,1687],{"class":50},"\"from_pandas.xlsx\"",[40,1689,316],{"class":195},[40,1691,1692],{"class":306},"engine",[40,1694,231],{"class":191},[40,1696,1697],{"class":50},"\"xlsxwriter\"",[40,1699,316],{"class":195},[40,1701,1702],{"class":306},"datetime_format",[40,1704,231],{"class":191},[40,1706,1707],{"class":50},"\"dd mmm yyyy\"",[40,1709,1710],{"class":195},") ",[40,1712,1583],{"class":191},[40,1714,1715],{"class":195}," xw:\n",[40,1717,1718,1721,1724,1726,1729],{"class":42,"line":260},[40,1719,1720],{"class":195},"    raw.to_excel(xw, ",[40,1722,1723],{"class":306},"index",[40,1725,231],{"class":191},[40,1727,1728],{"class":312},"False",[40,1730,240],{"class":195},[40,1732,1733,1736,1738,1740,1742],{"class":42,"line":265},[40,1734,1735],{"class":195},"check ",[40,1737,231],{"class":191},[40,1739,234],{"class":195},[40,1741,1687],{"class":50},[40,1743,1744],{"class":195},").active\n",[40,1746,1747,1749,1751,1753,1756,1758,1761,1763,1766,1768],{"class":42,"line":278},[40,1748,515],{"class":312},[40,1750,518],{"class":195},[40,1752,521],{"class":312},[40,1754,1755],{"class":195},"(check[",[40,1757,1313],{"class":50},[40,1759,1760],{"class":195},"].value).",[40,1762,527],{"class":312},[40,1764,1765],{"class":195},", check[",[40,1767,1313],{"class":50},[40,1769,1770],{"class":195},"].number_format)\n",[10,1772,1773,1774,1777,1778,1781,1782,1785,1786,1788,1789,1792,1793,1796,1797,1800,1801,1804,1805,1807,1808,1812],{},"The cell now holds a ",[37,1775,1776],{},"datetime"," with a date format, which is what the chart axis reads. Two details about the display format. ",[37,1779,1780],{},"to_datetime"," produces a datetime column, and ",[37,1783,1784],{},"ExcelWriter"," formats those with ",[37,1787,1702],{}," — its ",[37,1790,1791],{},"date_format"," applies only to plain ",[37,1794,1795],{},"date"," objects. And in pandas 3.0 the format is honoured by the xlsxwriter engine but not by the openpyxl engine, which writes ",[37,1798,1799],{},"YYYY-MM-DD HH:MM:SS"," whatever you pass; with openpyxl, set ",[37,1802,1803],{},"number_format"," on the date cells after writing. The chart itself reads the underlying date either way, but its tick labels inherit the cell format unless the axis sets its own ",[37,1806,1803],{}," — one more reason to set that explicitly. ",[19,1809,1811],{"href":1810},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fparse-excel-dates-into-python-datetimes-with-pandas\u002F","Parse Excel Dates into Python datetimes with pandas"," covers messier inputs.",[25,1814,1816],{"id":1815},"when-to-keep-the-text-axis","When to keep the text axis",[10,1818,1819],{},"A date axis is not always right. Trading days, business-day reports and anything where \"the next row\" is the natural step read better on a text axis, because a date axis leaves an empty slot for every weekend and holiday. Monthly or quarterly totals are evenly spaced anyway, so either axis draws them the same. Use the date axis when the spacing between dates carries meaning: inspections, incidents, measurements, orders from a small customer.",[25,1821,1823],{"id":1822},"common-pitfalls","Common pitfalls",[1825,1826,1827,1839,1853,1862,1868],"ul",{},[1828,1829,1830,1834,1835,1838],"li",{},[1831,1832,1833],"strong",{},"Dates stored as text."," A date axis needs real date values; convert with ",[37,1836,1837],{},"pd.to_datetime"," before writing.",[1828,1840,1841,1847,1848,731,1850,1852],{},[1831,1842,1843,1844,1846],{},"Missing ",[37,1845,613],{}," settings."," Set ",[37,1849,784],{},[37,1851,802],{}," or the axes are not paired.",[1828,1854,1855,1847,1858,1861],{},[1831,1856,1857],{},"Smoothed lines.",[37,1859,1860],{},"series.smooth = False","; smoothing invents values between measurements.",[1828,1863,1864,1867],{},[1831,1865,1866],{},"Timestamps within a day on a date axis."," They collapse onto the day; use a scatter chart.",[1828,1869,1870,1847,1873,1876],{},[1831,1871,1872],{},"Hidden axes in Excel.",[37,1874,1875],{},"delete = False"," on both axes, as for every openpyxl chart.",[25,1878,1880],{"id":1879},"conclusion","Conclusion",[10,1882,1883,1884,1886,1887,1889,1890,316,1892,1894,1895,1897],{},"When the gaps between dates matter, replace the default text axis with ",[37,1885,802],{},", set ",[37,1888,784],{},", pick ",[37,1891,730],{},[37,1893,734],{}," and a matching ",[37,1896,1803],{},", and turn smoothing off. Keep the text axis for trading days and regular periods, and use a scatter chart with a time-formatted number axis for readings within a day. Render the result to check the spacing tells the real story.",[25,1899,1901],{"id":1900},"frequently-asked-questions","Frequently asked questions",[10,1903,1904,1907],{},[1831,1905,1906],{},"How do I use a date axis in an openpyxl chart?","\nSet chart.x_axis = DateAxis(crossAx=100) and chart.y_axis.crossAx = 500, then set x_axis.number_format, majorTimeUnit and majorUnit. The dates in the category range must be real date values.",[10,1909,1910,1913],{},[1831,1911,1912],{},"Why are my dates evenly spaced on the chart?","\nopenpyxl line charts use a text axis by default, which gives each row one equal slot. Switch to a DateAxis to space points by their actual dates.",[10,1915,1916,1919],{},[1831,1917,1918],{},"How do I stop the chart line from curving?","\nSet series.smooth = False on each line series. openpyxl writes line series as smoothed unless told otherwise.",[10,1921,1922,1925],{},[1831,1923,1924],{},"How do I chart times within a single day?","\nUse a ScatterChart with the times as x values. Excel stores times as fractions of a day, so set the x axis number_format to hh:mm and a majorUnit such as 3\u002F24 for three-hourly ticks.",[25,1927,1929],{"id":1928},"related","Related",[1825,1931,1932,1938,1945,1952],{},[1828,1933,1934,1935,1937],{},"Up one level: ",[19,1936,22],{"href":21}," — every chart type openpyxl supports.",[1828,1939,1940,1944],{},[19,1941,1943],{"href":1942},"\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"," — line charts in general.",[1828,1946,1947,1951],{},[19,1948,1950],{"href":1949},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-stock-chart-with-openpyxl\u002F","Create a Stock Chart with openpyxl"," — where a text axis is the right choice.",[1828,1953,1954,1958],{},[19,1955,1957],{"href":1956},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates\u002F","Fix Excel Serial Numbers Showing Instead of Dates"," — dates as serial numbers, explained.",[1960,1961,1962],"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 .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}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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":35,"searchDepth":205,"depth":205,"links":1964},[1965,1966,1967,1968,1969,1970,1971,1972,1973,1974,1975,1976],{"id":27,"depth":205,"text":28},{"id":175,"depth":205,"text":176},{"id":547,"depth":205,"text":548},{"id":805,"depth":205,"text":806},{"id":894,"depth":205,"text":895},{"id":1117,"depth":205,"text":1118},{"id":1560,"depth":205,"text":1561},{"id":1815,"depth":205,"text":1816},{"id":1822,"depth":205,"text":1823},{"id":1879,"depth":205,"text":1880},{"id":1900,"depth":205,"text":1901},{"id":1928,"depth":205,"text":1929},"2026-09-29","Plot values against real time instead of evenly spaced labels: why irregular dates need a date axis, how to set one up in openpyxl with DateAxis and crossAx, tick units and label formats, straight lines instead of smoothed ones, when to keep a text axis, and why readings within a day need a scatter chart instead.","md",[1981,1983,1985,1987],{"q":1906,"a":1982},"Set chart.x_axis = DateAxis(crossAx=100) and chart.y_axis.crossAx = 500, then set x_axis.number_format, majorTimeUnit and majorUnit. The dates in the category range must be real date values.",{"q":1912,"a":1984},"openpyxl line charts use a text axis by default, which gives each row one equal slot. Switch to a DateAxis to space points by their actual dates.",{"q":1918,"a":1986},"Set series.smooth = False on each line series. openpyxl writes line series as smoothed unless told otherwise.",{"q":1924,"a":1988},"Use a ScatterChart with the times as x values. Excel stores times as fractions of a day, so set the x axis number_format to hh:mm and a majorUnit such as 3\u002F24 for three-hourly ticks.","use a date axis in an excel chart with openpyxl",null,{"breadcrumb":1992},[1993,1996,1999],{"name":1994,"item":1995},"Home","\u002F",{"name":1997,"item":1998},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl",{"title":2002,"description":2003},"Excel Chart Date Axis with openpyxl DateAxis","Use DateAxis in openpyxl so irregular dates are spaced by time: crossAx pairing, number_format, majorTimeUnit and majorUnit, smooth=False, text axes for trading days, and a ScatterChart for intraday timestamps.","use-a-date-axis-in-an-excel-chart-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-a-date-axis-in-an-excel-chart-with-openpyxl\u002Findex",[2007,2008],"charts-and-images","dates-and-times","how-to","ATjomEXqY1t9OynyvWnaFzHKWvuGTXOiLo8eGPSPZdo",[2012,2016],{"title":2013,"path":2014,"stem":2015,"children":-1},"Style Chart Colours and Data Labels with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl\u002Findex",{"title":2017,"path":2018,"stem":2019,"children":-1},"Use Multi-Level Category Labels in an Excel Chart","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-multi-level-category-labels-in-an-excel-chart","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fuse-multi-level-category-labels-in-an-excel-chart\u002Findex",[2021,2023],{"path":2022,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":2024,"title":1997},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2027,2030,2033,2036,2039,2042],{"path":2028,"title":2029,"kind":2009},"\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":2031,"title":2032,"kind":2009},"\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":2034,"title":2035,"kind":2009},"\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":2037,"title":2038,"kind":2009},"\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":2040,"title":2041,"kind":2009},"\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":2043,"title":2044,"kind":2009},"\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",1790972115382]