[{"data":1,"prerenderedAt":2252},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl":2220,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl":2227,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl":2232,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl":2233},{"id":4,"title":5,"body":6,"dateModified":2186,"datePublished":2186,"description":2187,"extension":2188,"faq":2189,"keyword":2198,"kind":2199,"meta":2200,"navigation":241,"path":2209,"seo":2210,"slug":2213,"stem":2214,"topics":2215,"type":2218,"__hash__":2219},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl\u002Findex.md","Rotate Text in Excel Cells with openpyxl",{"type":7,"value":8,"toc":2171},"minimark",[9,19,151,156,182,185,189,200,542,552,556,559,831,834,933,937,940,1332,1339,1590,1594,1602,1823,1830,1834,1849,2000,2011,2015,2031,2035,2043,2047,2081,2085,2091,2095,2106,2110,2116,2122,2128,2134,2138,2167],[10,11,12,13,18],"p",{},"A report with twelve months, thirty products or a column per day across the top has a layout problem: horizontal headers force wide columns, and the table no longer fits on the screen or the page. Rotating the header labels — angled at 45 degrees, or turned vertical — lets the columns shrink to the width of their numbers. Vertical text is also the standard way to label a block of rows along its left edge. openpyxl sets rotation through the cell's alignment; the details that trip people up are the numbering of the angles and the row height, which Excel does not adjust for a file written by Python. This page is part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002F","Styling Excel Cells with openpyxl",".",[20,21,29,30,29,34,29,38,29,46,29,54,29,61,29,68,29,71,29,75,29,80,29,85,29,87,29,92,29,94,29,97,29,100,29,104,29,106,29,110,29,112,29,115,29,118,29,122,29,124,29,128,29,130,29,133,29,136,29,140,29,142,29,146,29,148],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 208","img",[25,26],"rot-values-t","rot-values-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"What textRotation values mean",[35,36,37],"desc",{"id":26},"A table of textRotation values. 0 is normal horizontal text. 1 to 90 tilt the text upward, counter-clockwise, by that many degrees, so 45 is angled up and 90 reads bottom to top. 91 to 180 tilt it downward: 90 plus n means n degrees clockwise, so 135 is angled down 45 degrees and 180 reads top to bottom. 255 stacks the letters vertically without rotating them.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","208","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":42,"fill":45,"stroke":52,"style":53},"20.0","16.0","71.8","40.0","var(--line,#cdd5e6)","stroke-width:1px",[39,55],{"x":56,"y":49,"width":57,"height":51,"rx":42,"fill":58,"stroke":59,"style":60},"91.8","324.1","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[62,63,67],"text",{"x":64,"y":65,"style":66},"253.8","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Direction",[39,69],{"x":70,"y":49,"width":57,"height":51,"rx":42,"fill":58,"stroke":59,"style":60},"415.9",[62,72,74],{"x":73,"y":65,"style":66},"577.9","Example",[39,76],{"x":48,"y":77,"width":50,"height":78,"rx":42,"fill":79,"stroke":52,"style":53},"56.0","34.0","#e7ebef",[62,81,42],{"x":82,"y":83,"style":84},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[39,86],{"x":56,"y":77,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,88,91],{"x":64,"y":89,"style":90},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","horizontal",[39,93],{"x":70,"y":77,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,95,96],{"x":73,"y":89,"style":90},"normal text",[39,98],{"x":48,"y":99,"width":50,"height":78,"rx":42,"fill":79,"stroke":52,"style":53},"90.0",[62,101,103],{"x":82,"y":102,"style":84},"112.0","1–90",[39,105],{"x":56,"y":99,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,107,109],{"x":64,"y":108,"style":90},"111.0","upward",[39,111],{"x":70,"y":99,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,113,114],{"x":73,"y":108,"style":90},"45 = angled up; 90 = bottom to top",[39,116],{"x":48,"y":117,"width":50,"height":78,"rx":42,"fill":79,"stroke":52,"style":53},"124.0",[62,119,121],{"x":82,"y":120,"style":84},"146.0","91–180",[39,123],{"x":56,"y":117,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,125,127],{"x":64,"y":126,"style":90},"145.0","downward (90 + n)",[39,129],{"x":70,"y":117,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,131,132],{"x":73,"y":126,"style":90},"135 = 45° down; 180 = top to bottom",[39,134],{"x":48,"y":135,"width":50,"height":78,"rx":42,"fill":79,"stroke":52,"style":53},"158.0",[62,137,139],{"x":82,"y":138,"style":84},"180.0","255",[39,141],{"x":56,"y":135,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,143,145],{"x":64,"y":144,"style":90},"179.0","stacked letters",[39,147],{"x":70,"y":135,"width":57,"height":78,"rx":42,"fill":45,"stroke":52,"style":53},[62,149,150],{"x":73,"y":144,"style":90},"one letter per line",[152,153,155],"h2",{"id":154},"prerequisites","Prerequisites",[157,158,163],"pre",{"className":159,"code":160,"language":161,"meta":162,"style":162},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[164,165,166],"code",{"__ignoreMap":162},[167,168,171,175,179],"span",{"class":169,"line":170},"line",1,[167,172,174],{"class":173},"sMTad","pip",[167,176,178],{"class":177},"srMev"," install",[167,180,181],{"class":177}," openpyxl\n",[10,183,184],{},"The examples build a sales table with a column per month and a row per product, grouped by region.",[152,186,188],{"id":187},"step-1-angle-the-column-headers","Step 1 — Angle the column headers",[10,190,191,192,195,196,199],{},"Set ",[164,193,194],{},"textRotation"," in the header cells' ",[164,197,198],{},"Alignment",", then narrow the columns — the point of rotating is that the column no longer needs the width of the label:",[157,201,205],{"className":202,"code":203,"language":204,"meta":162,"style":162},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\nfrom openpyxl.styles import Alignment, Font\n\nmonths = [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\n          \"July\", \"August\", \"September\", \"October\", \"November\", \"December\"]\nwb = Workbook(); ws = wb.active; ws.title = \"Sales\"\nws[\"A1\"], ws[\"B1\"] = \"Region\", \"Product\"\nheader = Alignment(textRotation=45, horizontal=\"center\", vertical=\"bottom\")\nfor col, month in enumerate(months, start=3):\n    cell = ws.cell(row=1, column=col, value=month)\n    cell.alignment = header\n    cell.font = Font(bold=True)\n    ws.column_dimensions[cell.column_letter].width = 7\nprint(ws[\"C1\"].alignment.textRotation, ws.column_dimensions[\"C\"].width)\n","python",[164,206,207,223,236,243,287,321,342,370,412,441,478,489,510,521],{"__ignoreMap":162},[167,208,209,213,217,220],{"class":169,"line":170},[167,210,212],{"class":211},"s-kum","from",[167,214,216],{"class":215},"skGVy"," openpyxl ",[167,218,219],{"class":211},"import",[167,221,222],{"class":215}," Workbook\n",[167,224,226,228,231,233],{"class":169,"line":225},2,[167,227,212],{"class":211},[167,229,230],{"class":215}," openpyxl.styles ",[167,232,219],{"class":211},[167,234,235],{"class":215}," Alignment, Font\n",[167,237,239],{"class":169,"line":238},3,[167,240,242],{"emptyLinePlaceholder":241},true,"\n",[167,244,246,249,252,255,258,261,264,266,269,271,274,276,279,281,284],{"class":169,"line":245},4,[167,247,248],{"class":215},"months ",[167,250,251],{"class":211},"=",[167,253,254],{"class":215}," [",[167,256,257],{"class":177},"\"January\"",[167,259,260],{"class":215},", ",[167,262,263],{"class":177},"\"February\"",[167,265,260],{"class":215},[167,267,268],{"class":177},"\"March\"",[167,270,260],{"class":215},[167,272,273],{"class":177},"\"April\"",[167,275,260],{"class":215},[167,277,278],{"class":177},"\"May\"",[167,280,260],{"class":215},[167,282,283],{"class":177},"\"June\"",[167,285,286],{"class":215},",\n",[167,288,290,293,295,298,300,303,305,308,310,313,315,318],{"class":169,"line":289},5,[167,291,292],{"class":177},"          \"July\"",[167,294,260],{"class":215},[167,296,297],{"class":177},"\"August\"",[167,299,260],{"class":215},[167,301,302],{"class":177},"\"September\"",[167,304,260],{"class":215},[167,306,307],{"class":177},"\"October\"",[167,309,260],{"class":215},[167,311,312],{"class":177},"\"November\"",[167,314,260],{"class":215},[167,316,317],{"class":177},"\"December\"",[167,319,320],{"class":215},"]\n",[167,322,324,327,329,332,334,337,339],{"class":169,"line":323},6,[167,325,326],{"class":215},"wb ",[167,328,251],{"class":211},[167,330,331],{"class":215}," Workbook(); ws ",[167,333,251],{"class":211},[167,335,336],{"class":215}," wb.active; ws.title ",[167,338,251],{"class":211},[167,340,341],{"class":177}," \"Sales\"\n",[167,343,345,348,351,354,357,360,362,365,367],{"class":169,"line":344},7,[167,346,347],{"class":215},"ws[",[167,349,350],{"class":177},"\"A1\"",[167,352,353],{"class":215},"], ws[",[167,355,356],{"class":177},"\"B1\"",[167,358,359],{"class":215},"] ",[167,361,251],{"class":211},[167,363,364],{"class":177}," \"Region\"",[167,366,260],{"class":215},[167,368,369],{"class":177},"\"Product\"\n",[167,371,373,376,378,381,384,386,390,392,394,396,399,401,404,406,409],{"class":169,"line":372},8,[167,374,375],{"class":215},"header ",[167,377,251],{"class":211},[167,379,380],{"class":215}," Alignment(",[167,382,194],{"class":383},"sa561",[167,385,251],{"class":211},[167,387,389],{"class":388},"sP0c6","45",[167,391,260],{"class":215},[167,393,91],{"class":383},[167,395,251],{"class":211},[167,397,398],{"class":177},"\"center\"",[167,400,260],{"class":215},[167,402,403],{"class":383},"vertical",[167,405,251],{"class":211},[167,407,408],{"class":177},"\"bottom\"",[167,410,411],{"class":215},")\n",[167,413,415,418,421,424,427,430,433,435,438],{"class":169,"line":414},9,[167,416,417],{"class":211},"for",[167,419,420],{"class":215}," col, month ",[167,422,423],{"class":211},"in",[167,425,426],{"class":388}," enumerate",[167,428,429],{"class":215},"(months, ",[167,431,432],{"class":383},"start",[167,434,251],{"class":211},[167,436,437],{"class":388},"3",[167,439,440],{"class":215},"):\n",[167,442,444,447,449,452,455,457,460,462,465,467,470,473,475],{"class":169,"line":443},10,[167,445,446],{"class":215},"    cell ",[167,448,251],{"class":211},[167,450,451],{"class":215}," ws.cell(",[167,453,454],{"class":383},"row",[167,456,251],{"class":211},[167,458,459],{"class":388},"1",[167,461,260],{"class":215},[167,463,464],{"class":383},"column",[167,466,251],{"class":211},[167,468,469],{"class":215},"col, ",[167,471,472],{"class":383},"value",[167,474,251],{"class":211},[167,476,477],{"class":215},"month)\n",[167,479,481,484,486],{"class":169,"line":480},11,[167,482,483],{"class":215},"    cell.alignment ",[167,485,251],{"class":211},[167,487,488],{"class":215}," header\n",[167,490,492,495,497,500,503,505,508],{"class":169,"line":491},12,[167,493,494],{"class":215},"    cell.font ",[167,496,251],{"class":211},[167,498,499],{"class":215}," Font(",[167,501,502],{"class":383},"bold",[167,504,251],{"class":211},[167,506,507],{"class":388},"True",[167,509,411],{"class":215},[167,511,513,516,518],{"class":169,"line":512},13,[167,514,515],{"class":215},"    ws.column_dimensions[cell.column_letter].width ",[167,517,251],{"class":211},[167,519,520],{"class":388}," 7\n",[167,522,524,527,530,533,536,539],{"class":169,"line":523},14,[167,525,526],{"class":388},"print",[167,528,529],{"class":215},"(ws[",[167,531,532],{"class":177},"\"C1\"",[167,534,535],{"class":215},"].alignment.textRotation, ws.column_dimensions[",[167,537,538],{"class":177},"\"C\"",[167,540,541],{"class":215},"].width)\n",[10,543,544,545,548,549,551],{},"Forty-five degrees keeps the labels readable while cutting their horizontal footprint by about 30%; ninety degrees saves the most space but makes readers tilt their heads. ",[164,546,547],{},"horizontal=\"center\""," places each angled label over the middle of its column. Create one ",[164,550,198],{}," object and reuse it — the same object for every header cell is faster than a new one per cell.",[152,553,555],{"id":554},"step-2-give-the-header-row-enough-height","Step 2 — Give the header row enough height",[10,557,558],{},"In Excel, rotating text by hand makes the row grow to fit. openpyxl only records the rotation, so a file it writes keeps the default row height of 15 points, and the angled labels are clipped. Estimate the height from the longest label and the angle:",[157,560,562],{"className":202,"code":561,"language":204,"meta":162,"style":162},"import math\n\ndef rotated_row_height(labels, angle, font_size=11, padding=6):\n    if angle == 255:\n        return max(len(s) for s in labels) * font_size * 1.3 + padding\n    degrees = angle if angle \u003C= 90 else angle - 90\n    longest = max(len(s) for s in labels) * font_size * 0.55\n    line = font_size * 1.3\n    rad = math.radians(degrees)\n    return round(longest * math.sin(rad) + line * math.cos(rad) + padding, 1)\n\nws.row_dimensions[1].height = rotated_row_height(months, 45)\nprint(\"header row height:\", ws.row_dimensions[1].height, \"points\")\n",[164,563,564,571,575,602,619,663,694,726,740,750,786,790,809],{"__ignoreMap":162},[167,565,566,568],{"class":169,"line":170},[167,567,219],{"class":211},[167,569,570],{"class":215}," math\n",[167,572,573],{"class":169,"line":225},[167,574,242],{"emptyLinePlaceholder":241},[167,576,577,580,584,587,589,592,595,597,600],{"class":169,"line":238},[167,578,579],{"class":211},"def",[167,581,583],{"class":582},"s_Opv"," rotated_row_height",[167,585,586],{"class":215},"(labels, angle, font_size",[167,588,251],{"class":211},[167,590,591],{"class":388},"11",[167,593,594],{"class":215},", padding",[167,596,251],{"class":211},[167,598,599],{"class":388},"6",[167,601,440],{"class":215},[167,603,604,607,610,613,616],{"class":169,"line":245},[167,605,606],{"class":211},"    if",[167,608,609],{"class":215}," angle ",[167,611,612],{"class":211},"==",[167,614,615],{"class":388}," 255",[167,617,618],{"class":215},":\n",[167,620,621,624,627,630,633,636,638,641,643,646,649,652,654,657,660],{"class":169,"line":289},[167,622,623],{"class":211},"        return",[167,625,626],{"class":388}," max",[167,628,629],{"class":215},"(",[167,631,632],{"class":388},"len",[167,634,635],{"class":215},"(s) ",[167,637,417],{"class":211},[167,639,640],{"class":215}," s ",[167,642,423],{"class":211},[167,644,645],{"class":215}," labels) ",[167,647,648],{"class":211},"*",[167,650,651],{"class":215}," font_size ",[167,653,648],{"class":211},[167,655,656],{"class":388}," 1.3",[167,658,659],{"class":211}," +",[167,661,662],{"class":215}," padding\n",[167,664,665,668,670,672,675,677,680,683,686,688,691],{"class":169,"line":323},[167,666,667],{"class":215},"    degrees ",[167,669,251],{"class":211},[167,671,609],{"class":215},[167,673,674],{"class":211},"if",[167,676,609],{"class":215},[167,678,679],{"class":211},"\u003C=",[167,681,682],{"class":388}," 90",[167,684,685],{"class":211}," else",[167,687,609],{"class":215},[167,689,690],{"class":211},"-",[167,692,693],{"class":388}," 90\n",[167,695,696,699,701,703,705,707,709,711,713,715,717,719,721,723],{"class":169,"line":344},[167,697,698],{"class":215},"    longest ",[167,700,251],{"class":211},[167,702,626],{"class":388},[167,704,629],{"class":215},[167,706,632],{"class":388},[167,708,635],{"class":215},[167,710,417],{"class":211},[167,712,640],{"class":215},[167,714,423],{"class":211},[167,716,645],{"class":215},[167,718,648],{"class":211},[167,720,651],{"class":215},[167,722,648],{"class":211},[167,724,725],{"class":388}," 0.55\n",[167,727,728,731,733,735,737],{"class":169,"line":372},[167,729,730],{"class":215},"    line ",[167,732,251],{"class":211},[167,734,651],{"class":215},[167,736,648],{"class":211},[167,738,739],{"class":388}," 1.3\n",[167,741,742,745,747],{"class":169,"line":414},[167,743,744],{"class":215},"    rad ",[167,746,251],{"class":211},[167,748,749],{"class":215}," math.radians(degrees)\n",[167,751,752,755,758,761,763,766,769,772,774,777,779,782,784],{"class":169,"line":443},[167,753,754],{"class":211},"    return",[167,756,757],{"class":388}," round",[167,759,760],{"class":215},"(longest ",[167,762,648],{"class":211},[167,764,765],{"class":215}," math.sin(rad) ",[167,767,768],{"class":211},"+",[167,770,771],{"class":215}," line ",[167,773,648],{"class":211},[167,775,776],{"class":215}," math.cos(rad) ",[167,778,768],{"class":211},[167,780,781],{"class":215}," padding, ",[167,783,459],{"class":388},[167,785,411],{"class":215},[167,787,788],{"class":169,"line":480},[167,789,242],{"emptyLinePlaceholder":241},[167,791,792,795,797,800,802,805,807],{"class":169,"line":491},[167,793,794],{"class":215},"ws.row_dimensions[",[167,796,459],{"class":388},[167,798,799],{"class":215},"].height ",[167,801,251],{"class":211},[167,803,804],{"class":215}," rotated_row_height(months, ",[167,806,389],{"class":388},[167,808,411],{"class":215},[167,810,811,813,815,818,821,823,826,829],{"class":169,"line":512},[167,812,526],{"class":388},[167,814,629],{"class":215},[167,816,817],{"class":177},"\"header row height:\"",[167,819,820],{"class":215},", ws.row_dimensions[",[167,822,459],{"class":388},[167,824,825],{"class":215},"].height, ",[167,827,828],{"class":177},"\"points\"",[167,830,411],{"class":215},[10,832,833],{},"The estimate treats an average character as a little over half the font size wide, which holds for proportional fonts such as Calibri; wide capitals need a bit more. The vertical extent of rotated text is its length times the sine of the angle plus a line height times the cosine. For \"September\" at 45 degrees and 11-point type, that is about 54 points.",[20,835,29,841,29,844,29,847,29,852,29,859,29,870,29,876,29,880,29,884,29,889,29,891,29,897,29,902,29,906,29,910,29,913,29,918,29,924,29,929],{"viewBox":836,"role":23,"ariaLabelledBy":837,"xmlns":27,"style":840},"84 0 592 383",[838,839],"rot-height-t","rot-height-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,842,843],{"id":838},"How much height a rotated label needs",[35,845,846],{"id":839},"The height of a rotated header is built from three parts: the length of the longest label multiplied by the sine of the angle, the line height multiplied by the cosine of the angle, and a little padding. At 90 degrees the whole length becomes height; at 45 degrees about 70 percent of it does.",[39,848],{"x":849,"y":42,"width":850,"height":851,"fill":45},"84","592","383",[39,853],{"x":854,"y":49,"width":855,"height":856,"rx":857,"fill":79,"stroke":52,"style":858},"100.0","560.0","93.0","12","stroke-width:2px",[860,861,863],"g",{"transform":862},"translate(368.0,29.0) scale(1.000)",[864,865],"path",{"d":866,"fill":867,"stroke":868,"style":869},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[62,871,875],{"x":872,"y":873,"style":874},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Padding",[62,877,879],{"x":872,"y":878,"style":90},"95.0","a few points",[169,881],{"x1":872,"y1":882,"x2":872,"y2":883,"stroke":59,"style":858},"114.0","136.0",[885,886],"polygon",{"points":887,"fill":888},"380.0,140.0 375.0,131.0 385.0,131.0","#5b5cf0",[39,890],{"x":854,"y":126,"width":855,"height":856,"rx":857,"fill":58,"stroke":59,"style":858},[860,892,894],{"transform":893},"translate(368.0,158.0) scale(1.000)",[864,895],{"d":866,"fill":867,"stroke":896,"style":869},"var(--brand-strong,#4338ca)",[62,898,901],{"x":872,"y":899,"style":900},"204.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Line height × cos(angle)",[62,903,905],{"x":872,"y":904,"style":90},"224.0","the thickness of the text",[169,907],{"x1":872,"y1":908,"x2":872,"y2":909,"stroke":59,"style":858},"243.0","265.0",[885,911],{"points":912,"fill":888},"380.0,269.0 375.0,260.0 385.0,260.0",[39,914],{"x":854,"y":915,"width":855,"height":856,"rx":857,"fill":916,"stroke":917,"style":858},"274.0","#d9f4f1","var(--teal,#0f9488)",[860,919,921],{"transform":920},"translate(368.0,287.0) scale(1.000)",[864,922],{"d":866,"fill":867,"stroke":923,"style":869},"var(--teal-ink,#0b6157)",[62,925,928],{"x":872,"y":926,"style":927},"333.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Label length × sin(angle)",[62,930,932],{"x":872,"y":931,"style":90},"353.0","the longest label, e.g. September",[152,934,936],{"id":935},"step-3-label-a-block-of-rows-vertically","Step 3 — Label a block of rows vertically",[10,938,939],{},"A vertical label along the left of a group — the region for a block of product rows — is a merged cell with text rotated 90 degrees and centred both ways:",[157,941,943],{"className":202,"code":942,"language":204,"meta":162,"style":162},"products = [\"Brackets\", \"Hinges\", \"Panels\", \"Frames\"]\nrow = 2\nfor region in [\"North\", \"South\"]:\n    first = row\n    for product in products:\n        ws.cell(row=row, column=2, value=product)\n        for col in range(3, 15):\n            ws.cell(row=row, column=col, value=(row * 37 + col * 11) % 90 + 10)\n        row += 1\n    ws.merge_cells(start_row=first, start_column=1, end_row=row - 1, end_column=1)\n    label = ws.cell(row=first, column=1, value=region)\n    label.alignment = Alignment(textRotation=90, horizontal=\"center\", vertical=\"center\")\n    label.font = Font(bold=True)\nws.column_dimensions[\"A\"].width = 5\nws.column_dimensions[\"B\"].width = 12\nws.freeze_panes = \"C2\"\nwb.save(\"rotated_headers.xlsx\")\nprint(ws.merged_cells.ranges)\n",[164,944,945,974,984,1006,1016,1029,1057,1081,1134,1145,1190,1220,1254,1271,1287,1302,1313,1324],{"__ignoreMap":162},[167,946,947,950,952,954,957,959,962,964,967,969,972],{"class":169,"line":170},[167,948,949],{"class":215},"products ",[167,951,251],{"class":211},[167,953,254],{"class":215},[167,955,956],{"class":177},"\"Brackets\"",[167,958,260],{"class":215},[167,960,961],{"class":177},"\"Hinges\"",[167,963,260],{"class":215},[167,965,966],{"class":177},"\"Panels\"",[167,968,260],{"class":215},[167,970,971],{"class":177},"\"Frames\"",[167,973,320],{"class":215},[167,975,976,979,981],{"class":169,"line":225},[167,977,978],{"class":215},"row ",[167,980,251],{"class":211},[167,982,983],{"class":388}," 2\n",[167,985,986,988,991,993,995,998,1000,1003],{"class":169,"line":238},[167,987,417],{"class":211},[167,989,990],{"class":215}," region ",[167,992,423],{"class":211},[167,994,254],{"class":215},[167,996,997],{"class":177},"\"North\"",[167,999,260],{"class":215},[167,1001,1002],{"class":177},"\"South\"",[167,1004,1005],{"class":215},"]:\n",[167,1007,1008,1011,1013],{"class":169,"line":245},[167,1009,1010],{"class":215},"    first ",[167,1012,251],{"class":211},[167,1014,1015],{"class":215}," row\n",[167,1017,1018,1021,1024,1026],{"class":169,"line":289},[167,1019,1020],{"class":211},"    for",[167,1022,1023],{"class":215}," product ",[167,1025,423],{"class":211},[167,1027,1028],{"class":215}," products:\n",[167,1030,1031,1034,1036,1038,1041,1043,1045,1048,1050,1052,1054],{"class":169,"line":323},[167,1032,1033],{"class":215},"        ws.cell(",[167,1035,454],{"class":383},[167,1037,251],{"class":211},[167,1039,1040],{"class":215},"row, ",[167,1042,464],{"class":383},[167,1044,251],{"class":211},[167,1046,1047],{"class":388},"2",[167,1049,260],{"class":215},[167,1051,472],{"class":383},[167,1053,251],{"class":211},[167,1055,1056],{"class":215},"product)\n",[167,1058,1059,1062,1065,1067,1070,1072,1074,1076,1079],{"class":169,"line":344},[167,1060,1061],{"class":211},"        for",[167,1063,1064],{"class":215}," col ",[167,1066,423],{"class":211},[167,1068,1069],{"class":388}," range",[167,1071,629],{"class":215},[167,1073,437],{"class":388},[167,1075,260],{"class":215},[167,1077,1078],{"class":388},"15",[167,1080,440],{"class":215},[167,1082,1083,1086,1088,1090,1092,1094,1096,1098,1100,1102,1105,1107,1110,1112,1114,1116,1119,1122,1125,1127,1129,1132],{"class":169,"line":372},[167,1084,1085],{"class":215},"            ws.cell(",[167,1087,454],{"class":383},[167,1089,251],{"class":211},[167,1091,1040],{"class":215},[167,1093,464],{"class":383},[167,1095,251],{"class":211},[167,1097,469],{"class":215},[167,1099,472],{"class":383},[167,1101,251],{"class":211},[167,1103,1104],{"class":215},"(row ",[167,1106,648],{"class":211},[167,1108,1109],{"class":388}," 37",[167,1111,659],{"class":211},[167,1113,1064],{"class":215},[167,1115,648],{"class":211},[167,1117,1118],{"class":388}," 11",[167,1120,1121],{"class":215},") ",[167,1123,1124],{"class":211},"%",[167,1126,682],{"class":388},[167,1128,659],{"class":211},[167,1130,1131],{"class":388}," 10",[167,1133,411],{"class":215},[167,1135,1136,1139,1142],{"class":169,"line":414},[167,1137,1138],{"class":215},"        row ",[167,1140,1141],{"class":211},"+=",[167,1143,1144],{"class":388}," 1\n",[167,1146,1147,1150,1153,1155,1158,1161,1163,1165,1167,1170,1172,1174,1176,1179,1181,1184,1186,1188],{"class":169,"line":443},[167,1148,1149],{"class":215},"    ws.merge_cells(",[167,1151,1152],{"class":383},"start_row",[167,1154,251],{"class":211},[167,1156,1157],{"class":215},"first, ",[167,1159,1160],{"class":383},"start_column",[167,1162,251],{"class":211},[167,1164,459],{"class":388},[167,1166,260],{"class":215},[167,1168,1169],{"class":383},"end_row",[167,1171,251],{"class":211},[167,1173,978],{"class":215},[167,1175,690],{"class":211},[167,1177,1178],{"class":388}," 1",[167,1180,260],{"class":215},[167,1182,1183],{"class":383},"end_column",[167,1185,251],{"class":211},[167,1187,459],{"class":388},[167,1189,411],{"class":215},[167,1191,1192,1195,1197,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217],{"class":169,"line":480},[167,1193,1194],{"class":215},"    label ",[167,1196,251],{"class":211},[167,1198,451],{"class":215},[167,1200,454],{"class":383},[167,1202,251],{"class":211},[167,1204,1157],{"class":215},[167,1206,464],{"class":383},[167,1208,251],{"class":211},[167,1210,459],{"class":388},[167,1212,260],{"class":215},[167,1214,472],{"class":383},[167,1216,251],{"class":211},[167,1218,1219],{"class":215},"region)\n",[167,1221,1222,1225,1227,1229,1231,1233,1236,1238,1240,1242,1244,1246,1248,1250,1252],{"class":169,"line":491},[167,1223,1224],{"class":215},"    label.alignment ",[167,1226,251],{"class":211},[167,1228,380],{"class":215},[167,1230,194],{"class":383},[167,1232,251],{"class":211},[167,1234,1235],{"class":388},"90",[167,1237,260],{"class":215},[167,1239,91],{"class":383},[167,1241,251],{"class":211},[167,1243,398],{"class":177},[167,1245,260],{"class":215},[167,1247,403],{"class":383},[167,1249,251],{"class":211},[167,1251,398],{"class":177},[167,1253,411],{"class":215},[167,1255,1256,1259,1261,1263,1265,1267,1269],{"class":169,"line":512},[167,1257,1258],{"class":215},"    label.font ",[167,1260,251],{"class":211},[167,1262,499],{"class":215},[167,1264,502],{"class":383},[167,1266,251],{"class":211},[167,1268,507],{"class":388},[167,1270,411],{"class":215},[167,1272,1273,1276,1279,1282,1284],{"class":169,"line":523},[167,1274,1275],{"class":215},"ws.column_dimensions[",[167,1277,1278],{"class":177},"\"A\"",[167,1280,1281],{"class":215},"].width ",[167,1283,251],{"class":211},[167,1285,1286],{"class":388}," 5\n",[167,1288,1290,1292,1295,1297,1299],{"class":169,"line":1289},15,[167,1291,1275],{"class":215},[167,1293,1294],{"class":177},"\"B\"",[167,1296,1281],{"class":215},[167,1298,251],{"class":211},[167,1300,1301],{"class":388}," 12\n",[167,1303,1305,1308,1310],{"class":169,"line":1304},16,[167,1306,1307],{"class":215},"ws.freeze_panes ",[167,1309,251],{"class":211},[167,1311,1312],{"class":177}," \"C2\"\n",[167,1314,1316,1319,1322],{"class":169,"line":1315},17,[167,1317,1318],{"class":215},"wb.save(",[167,1320,1321],{"class":177},"\"rotated_headers.xlsx\"",[167,1323,411],{"class":215},[167,1325,1327,1329],{"class":169,"line":1326},18,[167,1328,526],{"class":388},[167,1330,1331],{"class":215},"(ws.merged_cells.ranges)\n",[10,1333,1334,1335,18],{},"The merged cell's formatting lives in its top-left cell, so the alignment goes on that cell. Column A can be narrow — five characters is plenty — because the label runs up the height of four rows. Freezing panes at C2 keeps both the angled headers and the region and product labels visible while scrolling; see ",[14,1336,1338],{"href":1337},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Ffreeze-header-row-in-excel-with-openpyxl\u002F","Freeze the Header Row in Excel with openpyxl",[20,1340,29,1346,29,1349,29,1352,29,1357,29,1362,29,1367,29,1371,29,1377,29,1381,29,1385,29,1389,29,1393,29,1397,29,1401,29,1405,29,1409,29,1413,29,1417,29,1421,29,1425,29,1427,29,1431,29,1433,29,1436,29,1439,29,1443,29,1445,29,1448,29,1450,29,1453,29,1455,29,1458,29,1460,29,1463,29,1465,29,1469,29,1471,29,1475,29,1477,29,1480,29,1482,29,1485,29,1487,29,1490,29,1492,29,1494,29,1497,29,1500,29,1502,29,1504,29,1507,29,1509,29,1512,29,1514,29,1517,29,1519,29,1522,29,1524,29,1526,29,1529,29,1532,29,1534,29,1536,29,1539,29,1541,29,1543,29,1545,29,1548,29,1550,29,1553,29,1555,29,1557,29,1560,29,1564,29,1566,29,1568,29,1571,29,1573,29,1576,29,1578,29,1581,29,1583,29,1586,29,1588],{"viewBox":1341,"role":23,"ariaLabelledBy":1342,"xmlns":27,"style":1345},"165 0 430 228",[1343,1344],"rot-sheet-t","rot-sheet-d","width:100%;max-width:430px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1347,1348],{"id":1343},"The finished layout — angled months, vertical region labels",[35,1350,1351],{"id":1344},"The Sales sheet. The first row holds Region, Product and month names angled at 45 degrees over narrow columns. Column A holds the region names North and South as vertical text in merged cells spanning four product rows each. Column B lists Brackets, Hinges, Panels and Frames.",[39,1353],{"x":1354,"y":42,"width":1355,"height":1356,"fill":45},"165","430","228",[62,1358,1361],{"x":872,"y":1359,"style":1360},"31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","rotated_headers.xlsx",[39,1363],{"x":1364,"y":1365,"width":82,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"181.3","48.0","24.0",[39,1368],{"x":1369,"y":1365,"width":1370,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"213.3","69.5",[62,1372,1376],{"x":1373,"y":1374,"style":1375},"248.1","65.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,1378],{"x":1379,"y":1365,"width":1380,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"282.8","78.7",[62,1382,1384],{"x":1383,"y":1374,"style":1375},"322.1","B",[39,1386],{"x":1387,"y":1365,"width":1388,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"361.5","57.1",[62,1390,1392],{"x":1391,"y":1374,"style":1375},"390.0","C",[39,1394],{"x":1395,"y":1365,"width":1396,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"418.5","57.4",[62,1398,1400],{"x":1399,"y":1374,"style":1375},"447.2","D",[39,1402],{"x":1403,"y":1365,"width":1404,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"475.9","58.7",[62,1406,1408],{"x":1407,"y":1374,"style":1375},"505.3","E",[39,1410],{"x":1411,"y":1365,"width":1412,"height":1366,"rx":42,"fill":79,"stroke":52,"style":53},"534.7","44.0",[62,1414,1416],{"x":1415,"y":1374,"style":1375},"556.7","F",[39,1418],{"x":1364,"y":1419,"width":82,"height":1420,"rx":42,"fill":79,"stroke":52,"style":53},"72.0","28.0",[62,1422,459],{"x":1423,"y":1424,"style":1375},"197.3","91.0",[39,1426],{"x":1369,"y":1419,"width":1370,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1428,1430],{"x":1373,"y":1424,"style":1429},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Region",[39,1432],{"x":1379,"y":1419,"width":1380,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1434,1435],{"x":1383,"y":1424,"style":1429},"Product",[39,1437],{"x":1387,"y":1419,"width":1388,"height":1420,"rx":42,"fill":58,"stroke":59,"style":1438},"stroke-width:1.5px",[62,1440,1442],{"x":1391,"y":1424,"style":1441},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Jan ↗",[39,1444],{"x":1395,"y":1419,"width":1396,"height":1420,"rx":42,"fill":58,"stroke":59,"style":1438},[62,1446,1447],{"x":1399,"y":1424,"style":1441},"Feb ↗",[39,1449],{"x":1403,"y":1419,"width":1404,"height":1420,"rx":42,"fill":58,"stroke":59,"style":1438},[62,1451,1452],{"x":1407,"y":1424,"style":1441},"Mar ↗",[39,1454],{"x":1411,"y":1419,"width":1412,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1456,1457],{"x":1415,"y":1424,"style":1429},"…",[39,1459],{"x":1364,"y":854,"width":82,"height":1420,"rx":42,"fill":79,"stroke":52,"style":53},[62,1461,1047],{"x":1423,"y":1462,"style":1375},"119.0",[39,1464],{"x":1369,"y":854,"width":1370,"height":1420,"rx":42,"fill":916,"stroke":917,"style":1438},[62,1466,1468],{"x":1373,"y":1462,"style":1467},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","North ↑",[39,1470],{"x":1379,"y":854,"width":1380,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1472,1474],{"x":1383,"y":1462,"style":1473},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Brackets",[39,1476],{"x":1387,"y":854,"width":1388,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1478,1479],{"x":1391,"y":1462,"style":1473},"27",[39,1481],{"x":1395,"y":854,"width":1396,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1483,1484],{"x":1399,"y":1462,"style":1473},"38",[39,1486],{"x":1403,"y":854,"width":1404,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1488,1489],{"x":1407,"y":1462,"style":1473},"49",[39,1491],{"x":1411,"y":854,"width":1412,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1493,1457],{"x":1415,"y":1462,"style":1473},[39,1495],{"x":1364,"y":1496,"width":82,"height":1420,"rx":42,"fill":79,"stroke":52,"style":53},"128.0",[62,1498,437],{"x":1423,"y":1499,"style":1375},"147.0",[39,1501],{"x":1369,"y":1496,"width":1370,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[39,1503],{"x":1379,"y":1496,"width":1380,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1505,1506],{"x":1383,"y":1499,"style":1473},"Hinges",[39,1508],{"x":1387,"y":1496,"width":1388,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1510,1511],{"x":1391,"y":1499,"style":1473},"64",[39,1513],{"x":1395,"y":1496,"width":1396,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1515,1516],{"x":1399,"y":1499,"style":1473},"75",[39,1518],{"x":1403,"y":1496,"width":1404,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1520,1521],{"x":1407,"y":1499,"style":1473},"86",[39,1523],{"x":1411,"y":1496,"width":1412,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1525,1457],{"x":1415,"y":1499,"style":1473},[39,1527],{"x":1364,"y":1528,"width":82,"height":1420,"rx":42,"fill":79,"stroke":52,"style":53},"156.0",[62,1530,41],{"x":1423,"y":1531,"style":1375},"175.0",[39,1533],{"x":1369,"y":1528,"width":1370,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[39,1535],{"x":1379,"y":1528,"width":1380,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1537,1538],{"x":1383,"y":1531,"style":1473},"Panels",[39,1540],{"x":1387,"y":1528,"width":1388,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1542,591],{"x":1391,"y":1531,"style":1473},[39,1544],{"x":1395,"y":1528,"width":1396,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1546,1547],{"x":1399,"y":1531,"style":1473},"22",[39,1549],{"x":1403,"y":1528,"width":1404,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1551,1552],{"x":1407,"y":1531,"style":1473},"33",[39,1554],{"x":1411,"y":1528,"width":1412,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1556,1457],{"x":1415,"y":1531,"style":1473},[39,1558],{"x":1364,"y":1559,"width":82,"height":1420,"rx":42,"fill":79,"stroke":52,"style":53},"184.0",[62,1561,1563],{"x":1423,"y":1562,"style":1375},"203.0","5",[39,1565],{"x":1369,"y":1559,"width":1370,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[39,1567],{"x":1379,"y":1559,"width":1380,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1569,1570],{"x":1383,"y":1562,"style":1473},"Frames",[39,1572],{"x":1387,"y":1559,"width":1388,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1574,1575],{"x":1391,"y":1562,"style":1473},"48",[39,1577],{"x":1395,"y":1559,"width":1396,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1579,1580],{"x":1399,"y":1562,"style":1473},"59",[39,1582],{"x":1403,"y":1559,"width":1404,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1584,1585],{"x":1407,"y":1562,"style":1473},"70",[39,1587],{"x":1411,"y":1559,"width":1412,"height":1420,"rx":42,"fill":45,"stroke":52,"style":53},[62,1589,1457],{"x":1415,"y":1562,"style":1473},[152,1591,1593],{"id":1592},"step-4-rotate-downward-and-stack-letters","Step 4 — Rotate downward and stack letters",[10,1595,1596,1597,1601],{},"Values from 91 to 180 tilt the text ",[1598,1599,1600],"em",{},"downward",": 90 plus the number of degrees below horizontal. And 255 is a special value for stacked text, letters one beneath another without rotation, sometimes used for short codes in narrow columns:",[157,1603,1605],{"className":202,"code":1604,"language":204,"meta":162,"style":162},"wb2 = Workbook(); ws2 = wb2.active\nfor col, (label, angle) in enumerate([(\"Up 45\", 45), (\"Down 45\", 135), (\"Up 90\", 90), (\"Down 90\", 180), (\"STACK\", 255)], start=1):\n    ws2.cell(row=1, column=col, value=label).alignment = Alignment(textRotation=angle, horizontal=\"center\")\n    ws2.column_dimensions[ws2.cell(row=1, column=col).column_letter].width = 8\nws2.row_dimensions[1].height = rotated_row_height([\"STACK\"], 255)\nwb2.save(\"rotation_samples.xlsx\")\nprint([c.alignment.textRotation for c in ws2[1]], ws2.row_dimensions[1].height)\n",[164,1606,1607,1622,1693,1738,1763,1786,1796],{"__ignoreMap":162},[167,1608,1609,1612,1614,1617,1619],{"class":169,"line":170},[167,1610,1611],{"class":215},"wb2 ",[167,1613,251],{"class":211},[167,1615,1616],{"class":215}," Workbook(); ws2 ",[167,1618,251],{"class":211},[167,1620,1621],{"class":215}," wb2.active\n",[167,1623,1624,1626,1629,1631,1633,1636,1639,1641,1643,1646,1649,1651,1654,1656,1659,1661,1663,1665,1668,1670,1673,1675,1678,1680,1682,1685,1687,1689,1691],{"class":169,"line":225},[167,1625,417],{"class":211},[167,1627,1628],{"class":215}," col, (label, angle) ",[167,1630,423],{"class":211},[167,1632,426],{"class":388},[167,1634,1635],{"class":215},"([(",[167,1637,1638],{"class":177},"\"Up 45\"",[167,1640,260],{"class":215},[167,1642,389],{"class":388},[167,1644,1645],{"class":215},"), (",[167,1647,1648],{"class":177},"\"Down 45\"",[167,1650,260],{"class":215},[167,1652,1653],{"class":388},"135",[167,1655,1645],{"class":215},[167,1657,1658],{"class":177},"\"Up 90\"",[167,1660,260],{"class":215},[167,1662,1235],{"class":388},[167,1664,1645],{"class":215},[167,1666,1667],{"class":177},"\"Down 90\"",[167,1669,260],{"class":215},[167,1671,1672],{"class":388},"180",[167,1674,1645],{"class":215},[167,1676,1677],{"class":177},"\"STACK\"",[167,1679,260],{"class":215},[167,1681,139],{"class":388},[167,1683,1684],{"class":215},")], ",[167,1686,432],{"class":383},[167,1688,251],{"class":211},[167,1690,459],{"class":388},[167,1692,440],{"class":215},[167,1694,1695,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716,1719,1721,1723,1725,1727,1730,1732,1734,1736],{"class":169,"line":238},[167,1696,1697],{"class":215},"    ws2.cell(",[167,1699,454],{"class":383},[167,1701,251],{"class":211},[167,1703,459],{"class":388},[167,1705,260],{"class":215},[167,1707,464],{"class":383},[167,1709,251],{"class":211},[167,1711,469],{"class":215},[167,1713,472],{"class":383},[167,1715,251],{"class":211},[167,1717,1718],{"class":215},"label).alignment ",[167,1720,251],{"class":211},[167,1722,380],{"class":215},[167,1724,194],{"class":383},[167,1726,251],{"class":211},[167,1728,1729],{"class":215},"angle, ",[167,1731,91],{"class":383},[167,1733,251],{"class":211},[167,1735,398],{"class":177},[167,1737,411],{"class":215},[167,1739,1740,1743,1745,1747,1749,1751,1753,1755,1758,1760],{"class":169,"line":245},[167,1741,1742],{"class":215},"    ws2.column_dimensions[ws2.cell(",[167,1744,454],{"class":383},[167,1746,251],{"class":211},[167,1748,459],{"class":388},[167,1750,260],{"class":215},[167,1752,464],{"class":383},[167,1754,251],{"class":211},[167,1756,1757],{"class":215},"col).column_letter].width ",[167,1759,251],{"class":211},[167,1761,1762],{"class":388}," 8\n",[167,1764,1765,1768,1770,1772,1774,1777,1779,1782,1784],{"class":169,"line":289},[167,1766,1767],{"class":215},"ws2.row_dimensions[",[167,1769,459],{"class":388},[167,1771,799],{"class":215},[167,1773,251],{"class":211},[167,1775,1776],{"class":215}," rotated_row_height([",[167,1778,1677],{"class":177},[167,1780,1781],{"class":215},"], ",[167,1783,139],{"class":388},[167,1785,411],{"class":215},[167,1787,1788,1791,1794],{"class":169,"line":323},[167,1789,1790],{"class":215},"wb2.save(",[167,1792,1793],{"class":177},"\"rotation_samples.xlsx\"",[167,1795,411],{"class":215},[167,1797,1798,1800,1803,1805,1808,1810,1813,1815,1818,1820],{"class":169,"line":344},[167,1799,526],{"class":388},[167,1801,1802],{"class":215},"([c.alignment.textRotation ",[167,1804,417],{"class":211},[167,1806,1807],{"class":215}," c ",[167,1809,423],{"class":211},[167,1811,1812],{"class":215}," ws2[",[167,1814,459],{"class":388},[167,1816,1817],{"class":215},"]], ws2.row_dimensions[",[167,1819,459],{"class":388},[167,1821,1822],{"class":215},"].height)\n",[10,1824,1825,1826,1829],{},"Any value outside 0–180 other than 255 is invalid and openpyxl raises a ",[164,1827,1828],{},"ValueError"," when you set it. Stacked text needs the most height of all — one line per letter — which is why the helper treats 255 separately; five letters need almost 80 points.",[152,1831,1833],{"id":1832},"the-same-in-xlsxwriter","The same in xlsxwriter",[10,1835,1836,1837,1840,1841,1844,1845,1848],{},"xlsxwriter uses signed angles instead: ",[164,1838,1839],{},"set_rotation(45)"," tilts upward, ",[164,1842,1843],{},"set_rotation(-45)"," downward, and ",[164,1846,1847],{},"set_rotation(270)"," stacks the text:",[157,1850,1852],{"className":202,"code":1851,"language":204,"meta":162,"style":162},"import xlsxwriter\n\nbook = xlsxwriter.Workbook(\"rotated_xlsxwriter.xlsx\")\nsheet = book.add_worksheet()\nangled = book.add_format({\"rotation\": 45, \"bold\": True, \"align\": \"center\"})\nsheet.set_row(0, rotated_row_height(months, 45))\nsheet.write_row(0, 0, months, angled)\nsheet.set_column(0, len(months) - 1, 7)\nbook.close()\nprint(\"written with rotation\", angled.rotation)\n",[164,1853,1854,1861,1865,1880,1890,1929,1944,1958,1983,1988],{"__ignoreMap":162},[167,1855,1856,1858],{"class":169,"line":170},[167,1857,219],{"class":211},[167,1859,1860],{"class":215}," xlsxwriter\n",[167,1862,1863],{"class":169,"line":225},[167,1864,242],{"emptyLinePlaceholder":241},[167,1866,1867,1870,1872,1875,1878],{"class":169,"line":238},[167,1868,1869],{"class":215},"book ",[167,1871,251],{"class":211},[167,1873,1874],{"class":215}," xlsxwriter.Workbook(",[167,1876,1877],{"class":177},"\"rotated_xlsxwriter.xlsx\"",[167,1879,411],{"class":215},[167,1881,1882,1885,1887],{"class":169,"line":245},[167,1883,1884],{"class":215},"sheet ",[167,1886,251],{"class":211},[167,1888,1889],{"class":215}," book.add_worksheet()\n",[167,1891,1892,1895,1897,1900,1903,1906,1908,1910,1913,1915,1917,1919,1922,1924,1926],{"class":169,"line":289},[167,1893,1894],{"class":215},"angled ",[167,1896,251],{"class":211},[167,1898,1899],{"class":215}," book.add_format({",[167,1901,1902],{"class":177},"\"rotation\"",[167,1904,1905],{"class":215},": ",[167,1907,389],{"class":388},[167,1909,260],{"class":215},[167,1911,1912],{"class":177},"\"bold\"",[167,1914,1905],{"class":215},[167,1916,507],{"class":388},[167,1918,260],{"class":215},[167,1920,1921],{"class":177},"\"align\"",[167,1923,1905],{"class":215},[167,1925,398],{"class":177},[167,1927,1928],{"class":215},"})\n",[167,1930,1931,1934,1936,1939,1941],{"class":169,"line":323},[167,1932,1933],{"class":215},"sheet.set_row(",[167,1935,42],{"class":388},[167,1937,1938],{"class":215},", rotated_row_height(months, ",[167,1940,389],{"class":388},[167,1942,1943],{"class":215},"))\n",[167,1945,1946,1949,1951,1953,1955],{"class":169,"line":344},[167,1947,1948],{"class":215},"sheet.write_row(",[167,1950,42],{"class":388},[167,1952,260],{"class":215},[167,1954,42],{"class":388},[167,1956,1957],{"class":215},", months, angled)\n",[167,1959,1960,1963,1965,1967,1969,1972,1974,1976,1978,1981],{"class":169,"line":372},[167,1961,1962],{"class":215},"sheet.set_column(",[167,1964,42],{"class":388},[167,1966,260],{"class":215},[167,1968,632],{"class":388},[167,1970,1971],{"class":215},"(months) ",[167,1973,690],{"class":211},[167,1975,1178],{"class":388},[167,1977,260],{"class":215},[167,1979,1980],{"class":388},"7",[167,1982,411],{"class":215},[167,1984,1985],{"class":169,"line":414},[167,1986,1987],{"class":215},"book.close()\n",[167,1989,1990,1992,1994,1997],{"class":169,"line":443},[167,1991,526],{"class":388},[167,1993,629],{"class":215},[167,1995,1996],{"class":177},"\"written with rotation\"",[167,1998,1999],{"class":215},", angled.rotation)\n",[10,2001,2002,2003,2006,2007,2010],{},"xlsxwriter converts the signed angle to Excel's own scale when it writes the file: ",[164,2004,2005],{},"-45"," ends up as 135 inside the workbook and ",[164,2008,2009],{},"270"," as 255 — the same values openpyxl reads back and uses.",[152,2012,2014],{"id":2013},"rotate-wrap-or-abbreviate","Rotate, wrap or abbreviate?",[10,2016,2017,2018,2022,2023,2026,2027,2030],{},"Rotation is one of three ways to fit long headers over narrow columns, and not always the best. ",[2019,2020,2021],"strong",{},"Wrapping"," keeps text horizontal and splits it over two or three lines — right for two-word headers such as \"Units sold\" but useless for single long words. ",[2019,2024,2025],{},"Abbreviating"," — Jan, Feb, Mar — is the most readable when a standard short form exists, and needs no extra height at all. ",[2019,2028,2029],{},"Rotating"," suits long single words, product names and dates where no abbreviation is obvious. Many reports combine them: abbreviated months rotated 45 degrees fit twelve columns into the width of four unrotated ones. When the sheet has an autofilter, leave a little extra height: the filter button sits in the bottom-right corner of each header cell and can cover the end of an angled label.",[152,2032,2034],{"id":2033},"rotated-text-when-printing","Rotated text when printing",[10,2036,2037,2038,2042],{},"Angled headers print exactly as they appear, which makes them a good fit for wide reports that must fit a page's width. Combine them with the page setup — landscape orientation and fit-to-width — and repeat the header row on every printed page, so the angled labels appear above the data on page two as well; ",[14,2039,2041],{"href":2040},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-print-area-and-page-setup-with-openpyxl\u002F","Set Print Area and Page Setup with openpyxl"," covers those settings.",[152,2044,2046],{"id":2045},"common-pitfalls","Common pitfalls",[2048,2049,2050,2057,2063,2069,2075],"ul",{},[2051,2052,2053,2056],"li",{},[2019,2054,2055],{},"Clipped labels."," Set the header row height; openpyxl does not grow it for you.",[2051,2058,2059,2062],{},[2019,2060,2061],{},"Negative angles in openpyxl."," Use 91–180 for downward text; negative numbers raise an error.",[2051,2064,2065,2068],{},[2019,2066,2067],{},"Rotating without narrowing the columns."," The space saving only happens if you reduce the widths.",[2051,2070,2071,2074],{},[2019,2072,2073],{},"Formatting the wrong cell of a merge."," Put the alignment on the top-left cell of the merged range.",[2051,2076,2077,2080],{},[2019,2078,2079],{},"Vertical text for long labels."," Past a dozen characters, a 45-degree angle reads more easily than 90.",[152,2082,2084],{"id":2083},"performance-notes","Performance notes",[10,2086,2087,2088,2090],{},"Rotation is a style property like any other, so reuse one ",[164,2089,198],{}," object across all the header cells; headers are a single row, so the cost is negligible either way.",[152,2092,2094],{"id":2093},"conclusion","Conclusion",[10,2096,2097,2098,2101,2102,2105],{},"Rotate header labels with ",[164,2099,2100],{},"Alignment(textRotation=45)"," and narrow the columns to their data, use 91–180 for downward text and 255 for stacked letters, and put vertical labels for row groups in merged cells rotated 90 degrees and centred. Set the header row height yourself from the longest label and the angle, because openpyxl does not resize rows for rotated text. In xlsxwriter, use the ",[164,2103,2104],{},"rotation"," format property with signed angles.",[152,2107,2109],{"id":2108},"frequently-asked-questions","Frequently asked questions",[10,2111,2112,2115],{},[2019,2113,2114],{},"How do I rotate text in a cell with openpyxl?","\nSet the cell's alignment with a textRotation value: cell.alignment = Alignment(textRotation=45). Values from 1 to 90 tilt the text upward, counter-clockwise, by that many degrees.",[10,2117,2118,2121],{},[2019,2119,2120],{},"How do I rotate text downward with openpyxl?","\nUse a value from 91 to 180, where 90 + n means n degrees downward (clockwise). textRotation=135 tilts the text 45 degrees downward, and 180 turns it to read straight down.",[10,2123,2124,2127],{},[2019,2125,2126],{},"How do I make vertical stacked text, one letter under another?","\nUse textRotation=255. Excel reserves that value for stacked text, where the letters are not rotated but placed one below the other. Make the row tall enough for every letter.",[10,2129,2130,2133],{},[2019,2131,2132],{},"Why is my rotated header text cut off?","\nRotation does not change the row height automatically when the file is written by Python. Set the header row's height yourself, based on the longest label and the angle.",[152,2135,2137],{"id":2136},"related","Related",[2048,2139,2140,2146,2153,2160],{},[2051,2141,2142,2143,2145],{},"Up one level: ",[14,2144,17],{"href":16}," — fonts, fills, borders and alignment.",[2051,2147,2148,2152],{},[14,2149,2151],{"href":2150},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fwrap-text-and-align-cells-with-openpyxl\u002F","Wrap Text and Align Cells with openpyxl"," — the other alignment options.",[2051,2154,2155,2159],{},[14,2156,2158],{"href":2157},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl\u002F","Set Column Width and Row Height in openpyxl"," — sizing rows and columns.",[2051,2161,2162,2166],{},[14,2163,2165],{"href":2164},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fmerge-cells-and-centre-a-report-title-with-openpyxl\u002F","Merge Cells and Centre a Report Title with openpyxl"," — merged labels.",[2168,2169,2170],"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 .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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":162,"searchDepth":225,"depth":225,"links":2172},[2173,2174,2175,2176,2177,2178,2179,2180,2181,2182,2183,2184,2185],{"id":154,"depth":225,"text":155},{"id":187,"depth":225,"text":188},{"id":554,"depth":225,"text":555},{"id":935,"depth":225,"text":936},{"id":1592,"depth":225,"text":1593},{"id":1832,"depth":225,"text":1833},{"id":2013,"depth":225,"text":2014},{"id":2033,"depth":225,"text":2034},{"id":2045,"depth":225,"text":2046},{"id":2083,"depth":225,"text":2084},{"id":2093,"depth":225,"text":2094},{"id":2108,"depth":225,"text":2109},{"id":2136,"depth":225,"text":2137},"2026-09-25","Angle header labels so a wide table of months, products or dates fits on screen, turn a side label vertical beside a block of rows, or stack letters top to bottom. Set textRotation with openpyxl — and understand its odd 0–180 and 255 values — size the header row so rotated text is not clipped, and do the same in xlsxwriter.","md",[2190,2192,2194,2196],{"q":2114,"a":2191},"Set the cell's alignment with a textRotation value: cell.alignment = Alignment(textRotation=45). Values from 1 to 90 tilt the text upward, counter-clockwise, by that many degrees.",{"q":2120,"a":2193},"Use a value from 91 to 180, where 90 + n means n degrees downward (clockwise). textRotation=135 tilts the text 45 degrees downward, and 180 turns it to read straight down.",{"q":2126,"a":2195},"Use textRotation=255. Excel reserves that value for stacked text, where the letters are not rotated but placed one below the other. Make the row tall enough for every letter.",{"q":2132,"a":2197},"Rotation does not change the row height automatically when the file is written by Python. Set the header row's height yourself, based on the longest label and the angle.","rotate text in excel cells with openpyxl",null,{"breadcrumb":2201},[2202,2205,2208],{"name":2203,"item":2204},"Home","\u002F",{"name":2206,"item":2207},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":17,"item":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl",{"title":2211,"description":2212},"Rotate Text and Angle Headers in Excel with openpyxl","Rotate cell text with openpyxl Alignment(textRotation=): 1–90 upward, 91–180 downward, 255 stacked vertical text, angled column headers with narrow columns, vertical labels in merged cells, row heights for rotated text and xlsxwriter set_rotation.","rotate-text-in-excel-cells-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Frotate-text-in-excel-cells-with-openpyxl\u002Findex",[2216,2217],"cell-styling","openpyxl","how-to","VjidnMRedVidBZPgsKn3aK3Gi-9cTTvRjeh6I4nj71Q",[2221,2224],{"title":2165,"path":2222,"stem":2223,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fmerge-cells-and-centre-a-report-title-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fmerge-cells-and-centre-a-report-title-with-openpyxl\u002Findex",{"title":2158,"path":2225,"stem":2226,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl\u002Findex",[2228,2230],{"path":2229,"title":2206},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":2231,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl",[],[2234,2237,2240,2243,2246,2249],{"path":2235,"title":2236,"kind":2218},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-rich-text-in-a-cell-with-xlsxwriter","Write Rich Text with Mixed Formats in One Cell with xlsxwriter",{"path":2238,"title":2239,"kind":2218},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fcopy-cell-styles-with-openpyxl","Copy Cell Styles from One Cell to Another with openpyxl",{"path":2241,"title":2242,"kind":2218},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas","Auto-Fit Column Widths When Writing with pandas",{"path":2244,"title":2245,"kind":2218},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fexport-a-styled-pandas-dataframe-to-excel","Export a Styled pandas DataFrame to Excel",{"path":2247,"title":2248,"kind":2218},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fgenerate-a-staff-rota-in-excel-with-python","Generate a Staff Rota in Excel with Python",{"path":2250,"title":2251,"kind":2218},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel","Add a Table of Contents Sheet with Hyperlinks in Excel",1790972118576]