[{"data":1,"prerenderedAt":2476},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter":2467},{"id":4,"title":5,"body":6,"dateModified":2444,"datePublished":2444,"description":2445,"extension":2446,"faq":2447,"meta":2458,"navigation":231,"path":2459,"seo":2460,"slug":2463,"stem":2464,"type":2465,"__hash__":2466},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter\u002Findex.md","Add Sparklines to an Excel Report with xlsxwriter",{"type":7,"value":8,"toc":2429},"minimark",[9,19,163,168,196,199,203,764,778,782,785,875,884,888,891,1048,1051,1117,1121,1241,1248,1252,1255,1579,1590,1594,1597,1981,1988,2055,2059,2067,2231,2240,2244,2299,2303,2311,2315,2334,2338,2344,2358,2371,2377,2383,2387,2425],[10,11,12,13,18],"p",{},"A table of twelve monthly columns tells a reader what happened; a sparkline in a thirteenth column tells them the shape of it at a glance. Sparklines are tiny in-cell charts, and xlsxwriter writes them directly, which makes them one of the cheapest additions to a generated report — a single call per row, no images, no chart objects, no extra file size to speak of. This guide adds all three sparkline types, shares scales so rows are comparable, and covers the openpyxl limitation that decides where in your pipeline they belong. It extends ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","Building Excel Reports with xlsxwriter",".",[20,21,29,30,29,34,29,38,29,46,29,53,29,59,29,68,29,75,29,80,29,84,29,87,29,92,29,97,29,102,29,107,29,112,29,117,29,121,29,126,29,129,29,133,29,135,29,141,29,144,29,148,29,150,29,152,29,154,29,156,29,158,29,160],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"44 -3 552 239","img",[25,26],"sp-t","sp-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"The three sparkline types on the same row of data",[35,36,37],"desc",{"id":26},"A line sparkline shows the trend's shape, a column sparkline emphasises each period's magnitude, and a win-loss sparkline reduces every period to above or below zero.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"44","-3","552","239","#ffffff",[47,48,52],"text",{"x":49,"y":50,"style":51},"380","26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Same twelve numbers, three questions answered",[47,54,58],{"x":55,"y":56,"style":57},"60","76","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","line",[39,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67},"180","52","220","40","5","#f0f2f5","var(--line,#cdd5e6)",[69,70],"polyline",{"points":71,"fill":72,"stroke":73,"style":74},"196,82 216,74 236,78 256,66 276,70 296,58 316,62 336,50 356,54 376,44","none","var(--brand,#5b5cf0)","stroke-width:2px",[47,76,79],{"x":77,"y":56,"style":78},"420","font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:start","what shape is the trend?",[47,81,83],{"x":55,"y":82,"style":57},"140","column",[39,85],{"x":61,"y":86,"width":63,"height":64,"rx":65,"fill":66,"stroke":67},"116",[39,88],{"x":89,"y":82,"width":90,"height":90,"fill":91},"194","12","#0f9488",[39,93],{"x":94,"y":95,"width":90,"height":96,"fill":91},"214","134","18",[39,98],{"x":99,"y":100,"width":90,"height":101,"fill":91},"234","138","14",[39,103],{"x":104,"y":105,"width":90,"height":106,"fill":91},"254","128","24",[39,108],{"x":109,"y":110,"width":90,"height":111,"fill":91},"274","132","20",[39,113],{"x":114,"y":115,"width":90,"height":116,"fill":91},"294","124","28",[39,118],{"x":119,"y":120,"width":90,"height":50,"fill":91},"314","126",[39,122],{"x":123,"y":124,"width":90,"height":125,"fill":91},"334","120","32",[47,127,128],{"x":77,"y":82,"style":78},"how big was each period?",[47,130,132],{"x":55,"y":131,"style":57},"204","win \u002F loss",[39,134],{"x":61,"y":61,"width":63,"height":64,"rx":65,"fill":66,"stroke":67},[58,136],{"x1":137,"y1":138,"x2":139,"y2":138,"stroke":67,"style":140},"188","200","392","stroke-width:1px",[39,142],{"x":89,"y":143,"width":90,"height":90,"fill":91},"186",[39,145],{"x":94,"y":146,"width":90,"height":90,"fill":147},"202","#dc2626",[39,149],{"x":99,"y":143,"width":90,"height":90,"fill":91},[39,151],{"x":104,"y":143,"width":90,"height":90,"fill":91},[39,153],{"x":109,"y":146,"width":90,"height":90,"fill":147},[39,155],{"x":114,"y":143,"width":90,"height":90,"fill":91},[39,157],{"x":119,"y":143,"width":90,"height":90,"fill":91},[39,159],{"x":123,"y":146,"width":90,"height":90,"fill":147},[47,161,162],{"x":77,"y":131,"style":78},"which periods missed target?",[164,165,167],"h2",{"id":166},"prerequisites","Prerequisites",[169,170,175],"pre",{"className":171,"code":172,"language":173,"meta":174,"style":174},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install xlsxwriter pandas\n","bash","",[176,177,178],"code",{"__ignoreMap":174},[179,180,182,186,190,193],"span",{"class":58,"line":181},1,[179,183,185],{"class":184},"sMTad","pip",[179,187,189],{"class":188},"srMev"," install",[179,191,192],{"class":188}," xlsxwriter",[179,194,195],{"class":188}," pandas\n",[10,197,198],{},"Sparklines are a write-time feature, so the workbook must be created by xlsxwriter — you cannot add them to an existing file with openpyxl.",[164,200,202],{"id":201},"add-a-line-sparkline-per-row","Add a line sparkline per row",[169,204,208],{"className":205,"code":206,"language":207,"meta":174,"style":174},"language-python shiki shiki-themes github-light github-dark-high-contrast","\"\"\"A monthly table with a trend column.\"\"\"\nimport xlsxwriter\n\nmonths = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"]\nrows = [\n    (\"North\", [120, 138, 131, 152, 149, 168]),\n    (\"South\", [98, 94, 102, 88, 91, 84]),\n    (\"East\",  [61, 66, 72, 70, 79, 88]),\n]\n\nwith xlsxwriter.Workbook(\"trends.xlsx\") as book:\n    ws = book.add_worksheet(\"Trends\")\n    bold = book.add_format({\"bold\": True})\n\n    ws.write_row(0, 0, [\"Region\"] + months + [\"Trend\"], bold)\n    for r, (region, values) in enumerate(rows, start=1):\n        ws.write(r, 0, region)\n        ws.write_row(r, 1, values)\n        ws.add_sparkline(r, len(months) + 1, {\n            \"range\": f\"Trends!B{r + 1}:G{r + 1}\",\n            \"type\": \"line\",\n            \"markers\": True,\n            \"high_point\": True,\n            \"low_point\": True,\n        })\n\n    ws.set_column(0, 0, 14)\n    ws.set_column(len(months) + 1, len(months) + 1, 16)\n    ws.set_row(0, 20)\n","python",[176,209,210,215,226,233,277,288,330,370,410,415,420,441,458,481,486,523,553,564,575,595,642,655,667,679,691,697,702,720,750],{"__ignoreMap":174},[179,211,212],{"class":58,"line":181},[179,213,214],{"class":188},"\"\"\"A monthly table with a trend column.\"\"\"\n",[179,216,218,222],{"class":58,"line":217},2,[179,219,221],{"class":220},"s-kum","import",[179,223,225],{"class":224},"skGVy"," xlsxwriter\n",[179,227,229],{"class":58,"line":228},3,[179,230,232],{"emptyLinePlaceholder":231},true,"\n",[179,234,236,239,242,245,248,251,254,256,259,261,264,266,269,271,274],{"class":58,"line":235},4,[179,237,238],{"class":224},"months ",[179,240,241],{"class":220},"=",[179,243,244],{"class":224}," [",[179,246,247],{"class":188},"\"Jan\"",[179,249,250],{"class":224},", ",[179,252,253],{"class":188},"\"Feb\"",[179,255,250],{"class":224},[179,257,258],{"class":188},"\"Mar\"",[179,260,250],{"class":224},[179,262,263],{"class":188},"\"Apr\"",[179,265,250],{"class":224},[179,267,268],{"class":188},"\"May\"",[179,270,250],{"class":224},[179,272,273],{"class":188},"\"Jun\"",[179,275,276],{"class":224},"]\n",[179,278,280,283,285],{"class":58,"line":279},5,[179,281,282],{"class":224},"rows ",[179,284,241],{"class":220},[179,286,287],{"class":224}," [\n",[179,289,291,294,297,300,303,305,307,309,312,314,317,319,322,324,327],{"class":58,"line":290},6,[179,292,293],{"class":224},"    (",[179,295,296],{"class":188},"\"North\"",[179,298,299],{"class":224},", [",[179,301,124],{"class":302},"sP0c6",[179,304,250],{"class":224},[179,306,100],{"class":302},[179,308,250],{"class":224},[179,310,311],{"class":302},"131",[179,313,250],{"class":224},[179,315,316],{"class":302},"152",[179,318,250],{"class":224},[179,320,321],{"class":302},"149",[179,323,250],{"class":224},[179,325,326],{"class":302},"168",[179,328,329],{"class":224},"]),\n",[179,331,333,335,338,340,343,345,348,350,353,355,358,360,363,365,368],{"class":58,"line":332},7,[179,334,293],{"class":224},[179,336,337],{"class":188},"\"South\"",[179,339,299],{"class":224},[179,341,342],{"class":302},"98",[179,344,250],{"class":224},[179,346,347],{"class":302},"94",[179,349,250],{"class":224},[179,351,352],{"class":302},"102",[179,354,250],{"class":224},[179,356,357],{"class":302},"88",[179,359,250],{"class":224},[179,361,362],{"class":302},"91",[179,364,250],{"class":224},[179,366,367],{"class":302},"84",[179,369,329],{"class":224},[179,371,373,375,378,381,384,386,389,391,394,396,399,401,404,406,408],{"class":58,"line":372},8,[179,374,293],{"class":224},[179,376,377],{"class":188},"\"East\"",[179,379,380],{"class":224},",  [",[179,382,383],{"class":302},"61",[179,385,250],{"class":224},[179,387,388],{"class":302},"66",[179,390,250],{"class":224},[179,392,393],{"class":302},"72",[179,395,250],{"class":224},[179,397,398],{"class":302},"70",[179,400,250],{"class":224},[179,402,403],{"class":302},"79",[179,405,250],{"class":224},[179,407,357],{"class":302},[179,409,329],{"class":224},[179,411,413],{"class":58,"line":412},9,[179,414,276],{"class":224},[179,416,418],{"class":58,"line":417},10,[179,419,232],{"emptyLinePlaceholder":231},[179,421,423,426,429,432,435,438],{"class":58,"line":422},11,[179,424,425],{"class":220},"with",[179,427,428],{"class":224}," xlsxwriter.Workbook(",[179,430,431],{"class":188},"\"trends.xlsx\"",[179,433,434],{"class":224},") ",[179,436,437],{"class":220},"as",[179,439,440],{"class":224}," book:\n",[179,442,444,447,449,452,455],{"class":58,"line":443},12,[179,445,446],{"class":224},"    ws ",[179,448,241],{"class":220},[179,450,451],{"class":224}," book.add_worksheet(",[179,453,454],{"class":188},"\"Trends\"",[179,456,457],{"class":224},")\n",[179,459,461,464,466,469,472,475,478],{"class":58,"line":460},13,[179,462,463],{"class":224},"    bold ",[179,465,241],{"class":220},[179,467,468],{"class":224}," book.add_format({",[179,470,471],{"class":188},"\"bold\"",[179,473,474],{"class":224},": ",[179,476,477],{"class":302},"True",[179,479,480],{"class":224},"})\n",[179,482,484],{"class":58,"line":483},14,[179,485,232],{"emptyLinePlaceholder":231},[179,487,489,492,495,497,499,501,504,507,510,513,515,517,520],{"class":58,"line":488},15,[179,490,491],{"class":224},"    ws.write_row(",[179,493,494],{"class":302},"0",[179,496,250],{"class":224},[179,498,494],{"class":302},[179,500,299],{"class":224},[179,502,503],{"class":188},"\"Region\"",[179,505,506],{"class":224},"] ",[179,508,509],{"class":220},"+",[179,511,512],{"class":224}," months ",[179,514,509],{"class":220},[179,516,244],{"class":224},[179,518,519],{"class":188},"\"Trend\"",[179,521,522],{"class":224},"], bold)\n",[179,524,526,529,532,535,538,541,545,547,550],{"class":58,"line":525},16,[179,527,528],{"class":220},"    for",[179,530,531],{"class":224}," r, (region, values) ",[179,533,534],{"class":220},"in",[179,536,537],{"class":302}," enumerate",[179,539,540],{"class":224},"(rows, ",[179,542,544],{"class":543},"sa561","start",[179,546,241],{"class":220},[179,548,549],{"class":302},"1",[179,551,552],{"class":224},"):\n",[179,554,556,559,561],{"class":58,"line":555},17,[179,557,558],{"class":224},"        ws.write(r, ",[179,560,494],{"class":302},[179,562,563],{"class":224},", region)\n",[179,565,567,570,572],{"class":58,"line":566},18,[179,568,569],{"class":224},"        ws.write_row(r, ",[179,571,549],{"class":302},[179,573,574],{"class":224},", values)\n",[179,576,578,581,584,587,589,592],{"class":58,"line":577},19,[179,579,580],{"class":224},"        ws.add_sparkline(r, ",[179,582,583],{"class":302},"len",[179,585,586],{"class":224},"(months) ",[179,588,509],{"class":220},[179,590,591],{"class":302}," 1",[179,593,594],{"class":224},", {\n",[179,596,598,601,603,606,609,613,616,618,620,623,626,628,630,632,634,636,639],{"class":58,"line":597},20,[179,599,600],{"class":188},"            \"range\"",[179,602,474],{"class":224},[179,604,605],{"class":220},"f",[179,607,608],{"class":188},"\"Trends!B",[179,610,612],{"class":611},"sSjpA","{",[179,614,615],{"class":224},"r ",[179,617,509],{"class":220},[179,619,591],{"class":302},[179,621,622],{"class":611},"}",[179,624,625],{"class":188},":G",[179,627,612],{"class":611},[179,629,615],{"class":224},[179,631,509],{"class":220},[179,633,591],{"class":302},[179,635,622],{"class":611},[179,637,638],{"class":188},"\"",[179,640,641],{"class":224},",\n",[179,643,645,648,650,653],{"class":58,"line":644},21,[179,646,647],{"class":188},"            \"type\"",[179,649,474],{"class":224},[179,651,652],{"class":188},"\"line\"",[179,654,641],{"class":224},[179,656,658,661,663,665],{"class":58,"line":657},22,[179,659,660],{"class":188},"            \"markers\"",[179,662,474],{"class":224},[179,664,477],{"class":302},[179,666,641],{"class":224},[179,668,670,673,675,677],{"class":58,"line":669},23,[179,671,672],{"class":188},"            \"high_point\"",[179,674,474],{"class":224},[179,676,477],{"class":302},[179,678,641],{"class":224},[179,680,682,685,687,689],{"class":58,"line":681},24,[179,683,684],{"class":188},"            \"low_point\"",[179,686,474],{"class":224},[179,688,477],{"class":302},[179,690,641],{"class":224},[179,692,694],{"class":58,"line":693},25,[179,695,696],{"class":224},"        })\n",[179,698,700],{"class":58,"line":699},26,[179,701,232],{"emptyLinePlaceholder":231},[179,703,705,708,710,712,714,716,718],{"class":58,"line":704},27,[179,706,707],{"class":224},"    ws.set_column(",[179,709,494],{"class":302},[179,711,250],{"class":224},[179,713,494],{"class":302},[179,715,250],{"class":224},[179,717,101],{"class":302},[179,719,457],{"class":224},[179,721,723,725,727,729,731,733,735,737,739,741,743,745,748],{"class":58,"line":722},28,[179,724,707],{"class":224},[179,726,583],{"class":302},[179,728,586],{"class":224},[179,730,509],{"class":220},[179,732,591],{"class":302},[179,734,250],{"class":224},[179,736,583],{"class":302},[179,738,586],{"class":224},[179,740,509],{"class":220},[179,742,591],{"class":302},[179,744,250],{"class":224},[179,746,747],{"class":302},"16",[179,749,457],{"class":224},[179,751,753,756,758,760,762],{"class":58,"line":752},29,[179,754,755],{"class":224},"    ws.set_row(",[179,757,494],{"class":302},[179,759,250],{"class":224},[179,761,111],{"class":302},[179,763,457],{"class":224},[10,765,766,769,770,773,774,777],{},[176,767,768],{},"range"," is a worksheet-qualified A1 range as a string, so the row number is one greater than the zero-based ",[176,771,772],{},"r"," used for ",[176,775,776],{},"add_sparkline",". Getting that off by one is the usual reason a sparkline shows the wrong row's shape.",[164,779,781],{"id":780},"share-a-scale-so-rows-are-comparable","Share a scale so rows are comparable",[10,783,784],{},"By default each sparkline scales to its own values, which makes a region with a range of 60–90 look identical to one ranging 900–1400. If the rows are meant to be compared, share the axis:",[169,786,788],{"className":205,"code":787,"language":207,"meta":174,"style":174},"ws.add_sparkline(r, 7, {\n    \"range\": f\"Trends!B{r + 1}:G{r + 1}\",\n    \"type\": \"line\",\n    \"max\": \"group\",\n    \"min\": \"group\",\n})\n",[176,789,790,800,837,848,860,871],{"__ignoreMap":174},[179,791,792,795,798],{"class":58,"line":181},[179,793,794],{"class":224},"ws.add_sparkline(r, ",[179,796,797],{"class":302},"7",[179,799,594],{"class":224},[179,801,802,805,807,809,811,813,815,817,819,821,823,825,827,829,831,833,835],{"class":58,"line":217},[179,803,804],{"class":188},"    \"range\"",[179,806,474],{"class":224},[179,808,605],{"class":220},[179,810,608],{"class":188},[179,812,612],{"class":611},[179,814,615],{"class":224},[179,816,509],{"class":220},[179,818,591],{"class":302},[179,820,622],{"class":611},[179,822,625],{"class":188},[179,824,612],{"class":611},[179,826,615],{"class":224},[179,828,509],{"class":220},[179,830,591],{"class":302},[179,832,622],{"class":611},[179,834,638],{"class":188},[179,836,641],{"class":224},[179,838,839,842,844,846],{"class":58,"line":228},[179,840,841],{"class":188},"    \"type\"",[179,843,474],{"class":224},[179,845,652],{"class":188},[179,847,641],{"class":224},[179,849,850,853,855,858],{"class":58,"line":235},[179,851,852],{"class":188},"    \"max\"",[179,854,474],{"class":224},[179,856,857],{"class":188},"\"group\"",[179,859,641],{"class":224},[179,861,862,865,867,869],{"class":58,"line":279},[179,863,864],{"class":188},"    \"min\"",[179,866,474],{"class":224},[179,868,857],{"class":188},[179,870,641],{"class":224},[179,872,873],{"class":58,"line":290},[179,874,480],{"class":224},[10,876,877,879,880,883],{},[176,878,857],{}," means \"the largest and smallest across every sparkline in this group\", so a flat row reads as flat rather than as dramatic. Use a fixed number instead — ",[176,881,882],{},"\"max\": 200"," — when successive months' reports must be comparable with each other, not just internally.",[164,885,887],{"id":886},"column-and-winloss-types","Column and win\u002Floss types",[10,889,890],{},"A column sparkline emphasises magnitude; a win\u002Floss sparkline throws magnitude away and shows only direction, which is exactly right for variance against target:",[169,892,894],{"className":205,"code":893,"language":207,"meta":174,"style":174},"ws.add_sparkline(r, 8, {\n    \"range\": f\"Trends!B{r + 1}:G{r + 1}\",\n    \"type\": \"column\",\n    \"style\": 12,\n})\n\nws.add_sparkline(r, 9, {\n    \"range\": f\"Variance!B{r + 1}:G{r + 1}\",     # values above\u002Fbelow zero\n    \"type\": \"win_loss\",\n    \"negative_points\": True,\n})\n",[176,895,896,905,941,952,963,967,971,980,1022,1033,1044],{"__ignoreMap":174},[179,897,898,900,903],{"class":58,"line":181},[179,899,794],{"class":224},[179,901,902],{"class":302},"8",[179,904,594],{"class":224},[179,906,907,909,911,913,915,917,919,921,923,925,927,929,931,933,935,937,939],{"class":58,"line":217},[179,908,804],{"class":188},[179,910,474],{"class":224},[179,912,605],{"class":220},[179,914,608],{"class":188},[179,916,612],{"class":611},[179,918,615],{"class":224},[179,920,509],{"class":220},[179,922,591],{"class":302},[179,924,622],{"class":611},[179,926,625],{"class":188},[179,928,612],{"class":611},[179,930,615],{"class":224},[179,932,509],{"class":220},[179,934,591],{"class":302},[179,936,622],{"class":611},[179,938,638],{"class":188},[179,940,641],{"class":224},[179,942,943,945,947,950],{"class":58,"line":228},[179,944,841],{"class":188},[179,946,474],{"class":224},[179,948,949],{"class":188},"\"column\"",[179,951,641],{"class":224},[179,953,954,957,959,961],{"class":58,"line":235},[179,955,956],{"class":188},"    \"style\"",[179,958,474],{"class":224},[179,960,90],{"class":302},[179,962,641],{"class":224},[179,964,965],{"class":58,"line":279},[179,966,480],{"class":224},[179,968,969],{"class":58,"line":290},[179,970,232],{"emptyLinePlaceholder":231},[179,972,973,975,978],{"class":58,"line":332},[179,974,794],{"class":224},[179,976,977],{"class":302},"9",[179,979,594],{"class":224},[179,981,982,984,986,988,991,993,995,997,999,1001,1003,1005,1007,1009,1011,1013,1015,1018],{"class":58,"line":372},[179,983,804],{"class":188},[179,985,474],{"class":224},[179,987,605],{"class":220},[179,989,990],{"class":188},"\"Variance!B",[179,992,612],{"class":611},[179,994,615],{"class":224},[179,996,509],{"class":220},[179,998,591],{"class":302},[179,1000,622],{"class":611},[179,1002,625],{"class":188},[179,1004,612],{"class":611},[179,1006,615],{"class":224},[179,1008,509],{"class":220},[179,1010,591],{"class":302},[179,1012,622],{"class":611},[179,1014,638],{"class":188},[179,1016,1017],{"class":224},",     ",[179,1019,1021],{"class":1020},"s-wDw","# values above\u002Fbelow zero\n",[179,1023,1024,1026,1028,1031],{"class":58,"line":412},[179,1025,841],{"class":188},[179,1027,474],{"class":224},[179,1029,1030],{"class":188},"\"win_loss\"",[179,1032,641],{"class":224},[179,1034,1035,1038,1040,1042],{"class":58,"line":417},[179,1036,1037],{"class":188},"    \"negative_points\"",[179,1039,474],{"class":224},[179,1041,477],{"class":302},[179,1043,641],{"class":224},[179,1045,1046],{"class":58,"line":422},[179,1047,480],{"class":224},[10,1049,1050],{},"For win\u002Floss to mean anything, the underlying cells must be signed — the difference from target, not the raw figure. Compute that column in pandas before writing.",[20,1052,29,1057,29,1060,29,1063,29,1066,29,1071,29,1076,29,1079,29,1085,29,1089,29,1094,29,1097,29,1100,29,1104,29,1108,29,1112,29,1114],{"viewBox":1053,"role":23,"ariaLabelledBy":1054,"xmlns":27,"style":28},"0 0 760 214",[1055,1056],"sp2-t","sp2-d",[31,1058,1059],{"id":1055},"Independent scaling versus a shared group scale",[35,1061,1062],{"id":1056},"With per-row scaling a small region's minor wobble looks identical to a large region's real growth, while a shared group scale renders both against the same axis.",[39,1064],{"x":494,"y":494,"width":1065,"height":94,"fill":45},"760",[47,1067,1070],{"x":1068,"y":116,"style":1069},"196","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","each row scaled alone",[47,1072,1075],{"x":1073,"y":116,"style":1074},"566","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","max\u002Fmin = \"group\"",[58,1077],{"x1":49,"y1":64,"x2":49,"y2":1078,"stroke":67,"style":140},"192",[39,1080],{"x":1081,"y":1082,"width":1083,"height":55,"rx":902,"fill":1084,"stroke":67},"34","48","322","#fee8f2",[69,1086],{"points":1087,"fill":72,"stroke":1088,"style":74},"60,92 110,72 160,80 210,60 260,66 320,52","var(--accent,#f43f8f)",[47,1090,1093],{"x":1091,"y":120,"style":1092},"195","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","North: 120 to 168",[39,1095],{"x":1081,"y":95,"width":1083,"height":1096,"rx":902,"fill":1084,"stroke":67},"58",[69,1098],{"points":1099,"fill":72,"stroke":1088,"style":74},"60,178 110,158 160,166 210,146 260,152 320,138",[39,1101],{"x":1102,"y":1082,"width":1083,"height":55,"rx":902,"fill":1103,"stroke":67},"404","#d9f4f1",[69,1105],{"points":1106,"fill":72,"stroke":1107,"style":74},"430,86 480,74 530,78 580,66 630,70 690,58","var(--teal,#0f9488)",[47,1109,1111],{"x":1110,"y":120,"style":1092},"565","East: 61 to 88, on the same axis",[39,1113],{"x":1102,"y":95,"width":1083,"height":1096,"rx":902,"fill":1103,"stroke":67},[69,1115],{"points":1116,"fill":72,"stroke":1107,"style":74},"430,186 480,184 530,182 580,180 630,179 690,176",[164,1118,1120],{"id":1119},"style-them-to-match-the-report","Style them to match the report",[169,1122,1124],{"className":205,"code":1123,"language":207,"meta":174,"style":174},"ws.add_sparkline(r, 7, {\n    \"range\": f\"Trends!B{r + 1}:G{r + 1}\",\n    \"type\": \"line\",\n    \"series_color\": \"#5B5CF0\",\n    \"high_point\": True,\n    \"first_point\": True,\n    \"last_point\": True,\n    \"weight\": 1.25,\n})\n",[176,1125,1126,1134,1170,1180,1192,1203,1214,1225,1237],{"__ignoreMap":174},[179,1127,1128,1130,1132],{"class":58,"line":181},[179,1129,794],{"class":224},[179,1131,797],{"class":302},[179,1133,594],{"class":224},[179,1135,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162,1164,1166,1168],{"class":58,"line":217},[179,1137,804],{"class":188},[179,1139,474],{"class":224},[179,1141,605],{"class":220},[179,1143,608],{"class":188},[179,1145,612],{"class":611},[179,1147,615],{"class":224},[179,1149,509],{"class":220},[179,1151,591],{"class":302},[179,1153,622],{"class":611},[179,1155,625],{"class":188},[179,1157,612],{"class":611},[179,1159,615],{"class":224},[179,1161,509],{"class":220},[179,1163,591],{"class":302},[179,1165,622],{"class":611},[179,1167,638],{"class":188},[179,1169,641],{"class":224},[179,1171,1172,1174,1176,1178],{"class":58,"line":228},[179,1173,841],{"class":188},[179,1175,474],{"class":224},[179,1177,652],{"class":188},[179,1179,641],{"class":224},[179,1181,1182,1185,1187,1190],{"class":58,"line":235},[179,1183,1184],{"class":188},"    \"series_color\"",[179,1186,474],{"class":224},[179,1188,1189],{"class":188},"\"#5B5CF0\"",[179,1191,641],{"class":224},[179,1193,1194,1197,1199,1201],{"class":58,"line":279},[179,1195,1196],{"class":188},"    \"high_point\"",[179,1198,474],{"class":224},[179,1200,477],{"class":302},[179,1202,641],{"class":224},[179,1204,1205,1208,1210,1212],{"class":58,"line":290},[179,1206,1207],{"class":188},"    \"first_point\"",[179,1209,474],{"class":224},[179,1211,477],{"class":302},[179,1213,641],{"class":224},[179,1215,1216,1219,1221,1223],{"class":58,"line":332},[179,1217,1218],{"class":188},"    \"last_point\"",[179,1220,474],{"class":224},[179,1222,477],{"class":302},[179,1224,641],{"class":224},[179,1226,1227,1230,1232,1235],{"class":58,"line":372},[179,1228,1229],{"class":188},"    \"weight\"",[179,1231,474],{"class":224},[179,1233,1234],{"class":302},"1.25",[179,1236,641],{"class":224},[179,1238,1239],{"class":58,"line":412},[179,1240,480],{"class":224},[10,1242,1243,1244,18],{},"Marking the first and last points gives a reader the endpoints without a numeric axis, which is most of what a sparkline is for. Keep the palette the same as the rest of the workbook — the shared-theme approach in ",[14,1245,1247],{"href":1246},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fapply-a-reusable-style-theme-across-an-excel-report\u002F","Apply a reusable style theme across an Excel report",[164,1249,1251],{"id":1250},"generate-from-a-dataframe","Generate from a DataFrame",[10,1253,1254],{},"In a real report the monthly columns come from a pivot, so write the frame and add one sparkline per data row:",[169,1256,1258],{"className":205,"code":1257,"language":207,"meta":174,"style":174},"import pandas as pd\n\npivot = df.pivot_table(index=\"region\", columns=\"month\", values=\"revenue\", aggfunc=\"sum\")\npivot = pivot.reindex(columns=months).fillna(0).round(0)\n\nwith pd.ExcelWriter(\"trends.xlsx\", engine=\"xlsxwriter\") as writer:\n    pivot.to_excel(writer, sheet_name=\"Trends\")\n    ws = writer.sheets[\"Trends\"]\n    last_col = len(pivot.columns)\n    for r in range(1, len(pivot) + 1):\n        ws.add_sparkline(r, last_col + 1, {\n            \"range\": f\"Trends!B{r + 1}:{chr(65 + last_col)}{r + 1}\",\n            \"type\": \"line\", \"max\": \"group\", \"min\": \"group\", \"markers\": True,\n        })\n    ws.set_column(last_col + 1, last_col + 1, 16)\n",[176,1259,1260,1272,1276,1326,1351,1355,1381,1395,1408,1421,1451,1462,1516,1553,1557],{"__ignoreMap":174},[179,1261,1262,1264,1267,1269],{"class":58,"line":181},[179,1263,221],{"class":220},[179,1265,1266],{"class":224}," pandas ",[179,1268,437],{"class":220},[179,1270,1271],{"class":224}," pd\n",[179,1273,1274],{"class":58,"line":217},[179,1275,232],{"emptyLinePlaceholder":231},[179,1277,1278,1281,1283,1286,1289,1291,1294,1296,1299,1301,1304,1306,1309,1311,1314,1316,1319,1321,1324],{"class":58,"line":228},[179,1279,1280],{"class":224},"pivot ",[179,1282,241],{"class":220},[179,1284,1285],{"class":224}," df.pivot_table(",[179,1287,1288],{"class":543},"index",[179,1290,241],{"class":220},[179,1292,1293],{"class":188},"\"region\"",[179,1295,250],{"class":224},[179,1297,1298],{"class":543},"columns",[179,1300,241],{"class":220},[179,1302,1303],{"class":188},"\"month\"",[179,1305,250],{"class":224},[179,1307,1308],{"class":543},"values",[179,1310,241],{"class":220},[179,1312,1313],{"class":188},"\"revenue\"",[179,1315,250],{"class":224},[179,1317,1318],{"class":543},"aggfunc",[179,1320,241],{"class":220},[179,1322,1323],{"class":188},"\"sum\"",[179,1325,457],{"class":224},[179,1327,1328,1330,1332,1335,1337,1339,1342,1344,1347,1349],{"class":58,"line":235},[179,1329,1280],{"class":224},[179,1331,241],{"class":220},[179,1333,1334],{"class":224}," pivot.reindex(",[179,1336,1298],{"class":543},[179,1338,241],{"class":220},[179,1340,1341],{"class":224},"months).fillna(",[179,1343,494],{"class":302},[179,1345,1346],{"class":224},").round(",[179,1348,494],{"class":302},[179,1350,457],{"class":224},[179,1352,1353],{"class":58,"line":279},[179,1354,232],{"emptyLinePlaceholder":231},[179,1356,1357,1359,1362,1364,1366,1369,1371,1374,1376,1378],{"class":58,"line":290},[179,1358,425],{"class":220},[179,1360,1361],{"class":224}," pd.ExcelWriter(",[179,1363,431],{"class":188},[179,1365,250],{"class":224},[179,1367,1368],{"class":543},"engine",[179,1370,241],{"class":220},[179,1372,1373],{"class":188},"\"xlsxwriter\"",[179,1375,434],{"class":224},[179,1377,437],{"class":220},[179,1379,1380],{"class":224}," writer:\n",[179,1382,1383,1386,1389,1391,1393],{"class":58,"line":332},[179,1384,1385],{"class":224},"    pivot.to_excel(writer, ",[179,1387,1388],{"class":543},"sheet_name",[179,1390,241],{"class":220},[179,1392,454],{"class":188},[179,1394,457],{"class":224},[179,1396,1397,1399,1401,1404,1406],{"class":58,"line":372},[179,1398,446],{"class":224},[179,1400,241],{"class":220},[179,1402,1403],{"class":224}," writer.sheets[",[179,1405,454],{"class":188},[179,1407,276],{"class":224},[179,1409,1410,1413,1415,1418],{"class":58,"line":412},[179,1411,1412],{"class":224},"    last_col ",[179,1414,241],{"class":220},[179,1416,1417],{"class":302}," len",[179,1419,1420],{"class":224},"(pivot.columns)\n",[179,1422,1423,1425,1428,1430,1433,1436,1438,1440,1442,1445,1447,1449],{"class":58,"line":417},[179,1424,528],{"class":220},[179,1426,1427],{"class":224}," r ",[179,1429,534],{"class":220},[179,1431,1432],{"class":302}," range",[179,1434,1435],{"class":224},"(",[179,1437,549],{"class":302},[179,1439,250],{"class":224},[179,1441,583],{"class":302},[179,1443,1444],{"class":224},"(pivot) ",[179,1446,509],{"class":220},[179,1448,591],{"class":302},[179,1450,552],{"class":224},[179,1452,1453,1456,1458,1460],{"class":58,"line":422},[179,1454,1455],{"class":224},"        ws.add_sparkline(r, last_col ",[179,1457,509],{"class":220},[179,1459,591],{"class":302},[179,1461,594],{"class":224},[179,1463,1464,1466,1468,1470,1472,1474,1476,1478,1480,1482,1485,1487,1490,1492,1495,1498,1501,1504,1506,1508,1510,1512,1514],{"class":58,"line":443},[179,1465,600],{"class":188},[179,1467,474],{"class":224},[179,1469,605],{"class":220},[179,1471,608],{"class":188},[179,1473,612],{"class":611},[179,1475,615],{"class":224},[179,1477,509],{"class":220},[179,1479,591],{"class":302},[179,1481,622],{"class":611},[179,1483,1484],{"class":188},":",[179,1486,612],{"class":611},[179,1488,1489],{"class":302},"chr",[179,1491,1435],{"class":224},[179,1493,1494],{"class":302},"65",[179,1496,1497],{"class":220}," +",[179,1499,1500],{"class":224}," last_col)",[179,1502,1503],{"class":611},"}{",[179,1505,615],{"class":224},[179,1507,509],{"class":220},[179,1509,591],{"class":302},[179,1511,622],{"class":611},[179,1513,638],{"class":188},[179,1515,641],{"class":224},[179,1517,1518,1520,1522,1524,1526,1529,1531,1533,1535,1538,1540,1542,1544,1547,1549,1551],{"class":58,"line":460},[179,1519,647],{"class":188},[179,1521,474],{"class":224},[179,1523,652],{"class":188},[179,1525,250],{"class":224},[179,1527,1528],{"class":188},"\"max\"",[179,1530,474],{"class":224},[179,1532,857],{"class":188},[179,1534,250],{"class":224},[179,1536,1537],{"class":188},"\"min\"",[179,1539,474],{"class":224},[179,1541,857],{"class":188},[179,1543,250],{"class":224},[179,1545,1546],{"class":188},"\"markers\"",[179,1548,474],{"class":224},[179,1550,477],{"class":302},[179,1552,641],{"class":224},[179,1554,1555],{"class":58,"line":483},[179,1556,696],{"class":224},[179,1558,1559,1562,1564,1566,1569,1571,1573,1575,1577],{"class":58,"line":488},[179,1560,1561],{"class":224},"    ws.set_column(last_col ",[179,1563,509],{"class":220},[179,1565,591],{"class":302},[179,1567,1568],{"class":224},", last_col ",[179,1570,509],{"class":220},[179,1572,591],{"class":302},[179,1574,250],{"class":224},[179,1576,747],{"class":302},[179,1578,457],{"class":224},[10,1580,1581,1582,1585,1586,18],{},"Note that ",[176,1583,1584],{},"pivot.to_excel"," writes the index, so the data starts in column B — which is why the range begins there. The pivot itself is covered in ",[14,1587,1589],{"href":1588},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a pivot table from Excel with pandas",[164,1591,1593],{"id":1592},"pair-each-sparkline-with-one-number","Pair each sparkline with one number",[10,1595,1596],{},"A trend line answers \"which way is this going?\" but not \"by how much?\". The combination that works in practice is a sparkline plus a single change figure beside it, so the reader gets direction and magnitude in one glance:",[169,1598,1600],{"className":205,"code":1599,"language":207,"meta":174,"style":174},"import pandas as pd\n\ndef change_columns(values: list[float]) -> tuple[float, float]:\n    \"\"\"Absolute and percentage change from the first period to the last.\"\"\"\n    first, last = values[0], values[-1]\n    delta = last - first\n    pct = (delta \u002F first) if first else 0.0\n    return delta, pct\n\nwith xlsxwriter.Workbook(\"trends.xlsx\") as book:\n    ws = book.add_worksheet(\"Trends\")\n    up = book.add_format({\"num_format\": \"+#,##0;-#,##0\", \"font_color\": \"#0B6157\"})\n    down = book.add_format({\"num_format\": \"+#,##0;-#,##0\", \"font_color\": \"#9C0006\"})\n    pct_fmt = book.add_format({\"num_format\": \"+0.0%;-0.0%\"})\n\n    for r, (region, values) in enumerate(rows, start=1):\n        delta, pct = change_columns(values)\n        ws.write(r, 0, region)\n        ws.write_row(r, 1, values)\n        ws.add_sparkline(r, 7, {\"range\": f\"Trends!B{r + 1}:G{r + 1}\",\n                                 \"type\": \"line\", \"max\": \"group\", \"min\": \"group\"})\n        ws.write_number(r, 8, delta, up if delta >= 0 else down)\n        ws.write_number(r, 9, pct, pct_fmt)\n",[176,1601,1602,1612,1616,1643,1648,1670,1685,1713,1721,1725,1739,1751,1780,1806,1824,1828,1848,1858,1866,1874,1918,1945,1972],{"__ignoreMap":174},[179,1603,1604,1606,1608,1610],{"class":58,"line":181},[179,1605,221],{"class":220},[179,1607,1266],{"class":224},[179,1609,437],{"class":220},[179,1611,1271],{"class":224},[179,1613,1614],{"class":58,"line":217},[179,1615,232],{"emptyLinePlaceholder":231},[179,1617,1618,1621,1625,1628,1631,1634,1636,1638,1640],{"class":58,"line":228},[179,1619,1620],{"class":220},"def",[179,1622,1624],{"class":1623},"s_Opv"," change_columns",[179,1626,1627],{"class":224},"(values: list[",[179,1629,1630],{"class":302},"float",[179,1632,1633],{"class":224},"]) -> tuple[",[179,1635,1630],{"class":302},[179,1637,250],{"class":224},[179,1639,1630],{"class":302},[179,1641,1642],{"class":224},"]:\n",[179,1644,1645],{"class":58,"line":235},[179,1646,1647],{"class":188},"    \"\"\"Absolute and percentage change from the first period to the last.\"\"\"\n",[179,1649,1650,1653,1655,1658,1660,1663,1666,1668],{"class":58,"line":279},[179,1651,1652],{"class":224},"    first, last ",[179,1654,241],{"class":220},[179,1656,1657],{"class":224}," values[",[179,1659,494],{"class":302},[179,1661,1662],{"class":224},"], values[",[179,1664,1665],{"class":220},"-",[179,1667,549],{"class":302},[179,1669,276],{"class":224},[179,1671,1672,1675,1677,1680,1682],{"class":58,"line":290},[179,1673,1674],{"class":224},"    delta ",[179,1676,241],{"class":220},[179,1678,1679],{"class":224}," last ",[179,1681,1665],{"class":220},[179,1683,1684],{"class":224}," first\n",[179,1686,1687,1690,1692,1695,1698,1701,1704,1707,1710],{"class":58,"line":332},[179,1688,1689],{"class":224},"    pct ",[179,1691,241],{"class":220},[179,1693,1694],{"class":224}," (delta ",[179,1696,1697],{"class":220},"\u002F",[179,1699,1700],{"class":224}," first) ",[179,1702,1703],{"class":220},"if",[179,1705,1706],{"class":224}," first ",[179,1708,1709],{"class":220},"else",[179,1711,1712],{"class":302}," 0.0\n",[179,1714,1715,1718],{"class":58,"line":372},[179,1716,1717],{"class":220},"    return",[179,1719,1720],{"class":224}," delta, pct\n",[179,1722,1723],{"class":58,"line":412},[179,1724,232],{"emptyLinePlaceholder":231},[179,1726,1727,1729,1731,1733,1735,1737],{"class":58,"line":417},[179,1728,425],{"class":220},[179,1730,428],{"class":224},[179,1732,431],{"class":188},[179,1734,434],{"class":224},[179,1736,437],{"class":220},[179,1738,440],{"class":224},[179,1740,1741,1743,1745,1747,1749],{"class":58,"line":422},[179,1742,446],{"class":224},[179,1744,241],{"class":220},[179,1746,451],{"class":224},[179,1748,454],{"class":188},[179,1750,457],{"class":224},[179,1752,1753,1756,1758,1760,1763,1765,1768,1770,1773,1775,1778],{"class":58,"line":443},[179,1754,1755],{"class":224},"    up ",[179,1757,241],{"class":220},[179,1759,468],{"class":224},[179,1761,1762],{"class":188},"\"num_format\"",[179,1764,474],{"class":224},[179,1766,1767],{"class":188},"\"+#,##0;-#,##0\"",[179,1769,250],{"class":224},[179,1771,1772],{"class":188},"\"font_color\"",[179,1774,474],{"class":224},[179,1776,1777],{"class":188},"\"#0B6157\"",[179,1779,480],{"class":224},[179,1781,1782,1785,1787,1789,1791,1793,1795,1797,1799,1801,1804],{"class":58,"line":460},[179,1783,1784],{"class":224},"    down ",[179,1786,241],{"class":220},[179,1788,468],{"class":224},[179,1790,1762],{"class":188},[179,1792,474],{"class":224},[179,1794,1767],{"class":188},[179,1796,250],{"class":224},[179,1798,1772],{"class":188},[179,1800,474],{"class":224},[179,1802,1803],{"class":188},"\"#9C0006\"",[179,1805,480],{"class":224},[179,1807,1808,1811,1813,1815,1817,1819,1822],{"class":58,"line":483},[179,1809,1810],{"class":224},"    pct_fmt ",[179,1812,241],{"class":220},[179,1814,468],{"class":224},[179,1816,1762],{"class":188},[179,1818,474],{"class":224},[179,1820,1821],{"class":188},"\"+0.0%;-0.0%\"",[179,1823,480],{"class":224},[179,1825,1826],{"class":58,"line":488},[179,1827,232],{"emptyLinePlaceholder":231},[179,1829,1830,1832,1834,1836,1838,1840,1842,1844,1846],{"class":58,"line":525},[179,1831,528],{"class":220},[179,1833,531],{"class":224},[179,1835,534],{"class":220},[179,1837,537],{"class":302},[179,1839,540],{"class":224},[179,1841,544],{"class":543},[179,1843,241],{"class":220},[179,1845,549],{"class":302},[179,1847,552],{"class":224},[179,1849,1850,1853,1855],{"class":58,"line":555},[179,1851,1852],{"class":224},"        delta, pct ",[179,1854,241],{"class":220},[179,1856,1857],{"class":224}," change_columns(values)\n",[179,1859,1860,1862,1864],{"class":58,"line":566},[179,1861,558],{"class":224},[179,1863,494],{"class":302},[179,1865,563],{"class":224},[179,1867,1868,1870,1872],{"class":58,"line":577},[179,1869,569],{"class":224},[179,1871,549],{"class":302},[179,1873,574],{"class":224},[179,1875,1876,1878,1880,1883,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916],{"class":58,"line":597},[179,1877,580],{"class":224},[179,1879,797],{"class":302},[179,1881,1882],{"class":224},", {",[179,1884,1885],{"class":188},"\"range\"",[179,1887,474],{"class":224},[179,1889,605],{"class":220},[179,1891,608],{"class":188},[179,1893,612],{"class":611},[179,1895,615],{"class":224},[179,1897,509],{"class":220},[179,1899,591],{"class":302},[179,1901,622],{"class":611},[179,1903,625],{"class":188},[179,1905,612],{"class":611},[179,1907,615],{"class":224},[179,1909,509],{"class":220},[179,1911,591],{"class":302},[179,1913,622],{"class":611},[179,1915,638],{"class":188},[179,1917,641],{"class":224},[179,1919,1920,1923,1925,1927,1929,1931,1933,1935,1937,1939,1941,1943],{"class":58,"line":644},[179,1921,1922],{"class":188},"                                 \"type\"",[179,1924,474],{"class":224},[179,1926,652],{"class":188},[179,1928,250],{"class":224},[179,1930,1528],{"class":188},[179,1932,474],{"class":224},[179,1934,857],{"class":188},[179,1936,250],{"class":224},[179,1938,1537],{"class":188},[179,1940,474],{"class":224},[179,1942,857],{"class":188},[179,1944,480],{"class":224},[179,1946,1947,1950,1952,1955,1957,1960,1963,1966,1969],{"class":58,"line":657},[179,1948,1949],{"class":224},"        ws.write_number(r, ",[179,1951,902],{"class":302},[179,1953,1954],{"class":224},", delta, up ",[179,1956,1703],{"class":220},[179,1958,1959],{"class":224}," delta ",[179,1961,1962],{"class":220},">=",[179,1964,1965],{"class":302}," 0",[179,1967,1968],{"class":220}," else",[179,1970,1971],{"class":224}," down)\n",[179,1973,1974,1976,1978],{"class":58,"line":669},[179,1975,1949],{"class":224},[179,1977,977],{"class":302},[179,1979,1980],{"class":224},", pct, pct_fmt)\n",[10,1982,1983,1984,1987],{},"The ",[176,1985,1986],{},"+#,##0;-#,##0"," format shows the sign explicitly, which stops a reader having to work out whether −4 is a fall or a negative balance. Colouring the two cases differently is the smallest possible conditional format and needs no rule at all.",[20,1989,29,1994,29,1997,29,2000,29,2002,29,2005,29,2008,29,2013,29,2017,29,2020,29,2024,29,2029,29,2033,29,2037,29,2040,29,2044,29,2047,29,2050],{"viewBox":1990,"role":23,"ariaLabelledBy":1991,"xmlns":27,"style":28},"0 0 760 200",[1992,1993],"sp3-t","sp3-d",[31,1995,1996],{"id":1992},"A row that answers both questions at once",[35,1998,1999],{"id":1993},"The monthly figures give detail, the sparkline gives shape, and the signed change column gives magnitude, so no reader has to compute anything.",[39,2001],{"x":494,"y":494,"width":1065,"height":138,"fill":45},[47,2003,2004],{"x":49,"y":50,"style":51},"Detail, shape and magnitude in one row",[39,2006],{"x":64,"y":62,"width":138,"height":41,"rx":2007,"fill":66,"stroke":67},"6",[47,2009,2012],{"x":82,"y":2010,"style":2011},"80","font-size:12px;fill:var(--text,#172033);text-anchor:middle","120  138  131  152  168",[39,2014],{"x":2015,"y":62,"width":61,"height":41,"rx":2007,"fill":2016,"stroke":67},"248","#ebebfd",[69,2018],{"points":2019,"fill":72,"stroke":73,"style":74},"266,86 300,76 334,80 368,64 410,58",[39,2021],{"x":2022,"y":62,"width":2023,"height":41,"rx":2007,"fill":1103,"stroke":67},"436","130",[47,2025,2028],{"x":2026,"y":2010,"style":2027},"501","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","+48",[39,2030],{"x":2031,"y":62,"width":2032,"height":41,"rx":2007,"fill":1103,"stroke":67},"574","146",[47,2034,2036],{"x":2035,"y":2010,"style":2027},"647","+40.0%",[47,2038,2039],{"x":82,"y":120,"style":1092},"the numbers",[47,2041,2043],{"x":2042,"y":120,"style":1092},"338","the shape",[47,2045,2046],{"x":2026,"y":120,"style":1092},"the change",[47,2048,2049],{"x":2035,"y":120,"style":1092},"in context",[47,2051,2054],{"x":49,"y":2052,"style":2053},"172","font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","Each column answers a question the previous one raises",[164,2056,2058],{"id":2057},"group-them-so-the-whole-column-shares-one-definition","Group them so the whole column shares one definition",[10,2060,2061,2062,2066],{},"Adding a sparkline per row works and is easy to follow, but Excel also supports a ",[2063,2064,2065],"em",{},"group",": one definition covering a block of rows, which is what the UI creates when you drag a sparkline down. xlsxwriter expresses it by passing lists instead of single ranges:",[169,2068,2070],{"className":205,"code":2069,"language":207,"meta":174,"style":174},"ws.add_sparkline(1, 7, {\n    \"location\": [f\"H{r}\" for r in range(2, len(rows) + 2)],\n    \"range\":    [f\"Trends!B{r}:G{r}\" for r in range(2, len(rows) + 2)],\n    \"type\": \"line\",\n    \"markers\": True,\n    \"max\": \"group\",\n    \"min\": \"group\",\n})\n",[176,2071,2072,2085,2135,2186,2196,2207,2217,2227],{"__ignoreMap":174},[179,2073,2074,2077,2079,2081,2083],{"class":58,"line":181},[179,2075,2076],{"class":224},"ws.add_sparkline(",[179,2078,549],{"class":302},[179,2080,250],{"class":224},[179,2082,797],{"class":302},[179,2084,594],{"class":224},[179,2086,2087,2090,2093,2095,2098,2100,2102,2104,2106,2109,2111,2113,2115,2117,2120,2122,2124,2127,2129,2132],{"class":58,"line":217},[179,2088,2089],{"class":188},"    \"location\"",[179,2091,2092],{"class":224},": [",[179,2094,605],{"class":220},[179,2096,2097],{"class":188},"\"H",[179,2099,612],{"class":611},[179,2101,772],{"class":224},[179,2103,622],{"class":611},[179,2105,638],{"class":188},[179,2107,2108],{"class":220}," for",[179,2110,1427],{"class":224},[179,2112,534],{"class":220},[179,2114,1432],{"class":302},[179,2116,1435],{"class":224},[179,2118,2119],{"class":302},"2",[179,2121,250],{"class":224},[179,2123,583],{"class":302},[179,2125,2126],{"class":224},"(rows) ",[179,2128,509],{"class":220},[179,2130,2131],{"class":302}," 2",[179,2133,2134],{"class":224},")],\n",[179,2136,2137,2139,2142,2144,2146,2148,2150,2152,2154,2156,2158,2160,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180,2182,2184],{"class":58,"line":228},[179,2138,804],{"class":188},[179,2140,2141],{"class":224},":    [",[179,2143,605],{"class":220},[179,2145,608],{"class":188},[179,2147,612],{"class":611},[179,2149,772],{"class":224},[179,2151,622],{"class":611},[179,2153,625],{"class":188},[179,2155,612],{"class":611},[179,2157,772],{"class":224},[179,2159,622],{"class":611},[179,2161,638],{"class":188},[179,2163,2108],{"class":220},[179,2165,1427],{"class":224},[179,2167,534],{"class":220},[179,2169,1432],{"class":302},[179,2171,1435],{"class":224},[179,2173,2119],{"class":302},[179,2175,250],{"class":224},[179,2177,583],{"class":302},[179,2179,2126],{"class":224},[179,2181,509],{"class":220},[179,2183,2131],{"class":302},[179,2185,2134],{"class":224},[179,2187,2188,2190,2192,2194],{"class":58,"line":235},[179,2189,841],{"class":188},[179,2191,474],{"class":224},[179,2193,652],{"class":188},[179,2195,641],{"class":224},[179,2197,2198,2201,2203,2205],{"class":58,"line":279},[179,2199,2200],{"class":188},"    \"markers\"",[179,2202,474],{"class":224},[179,2204,477],{"class":302},[179,2206,641],{"class":224},[179,2208,2209,2211,2213,2215],{"class":58,"line":290},[179,2210,852],{"class":188},[179,2212,474],{"class":224},[179,2214,857],{"class":188},[179,2216,641],{"class":224},[179,2218,2219,2221,2223,2225],{"class":58,"line":332},[179,2220,864],{"class":188},[179,2222,474],{"class":224},[179,2224,857],{"class":188},[179,2226,641],{"class":224},[179,2228,2229],{"class":58,"line":372},[179,2230,480],{"class":224},[10,2232,2233,2236,2237,2239],{},[176,2234,2235],{},"location"," and ",[176,2238,768],{}," must be the same length and in the same order — each location takes its data from the corresponding range. A group is slightly smaller in the file and, more usefully, appears in Excel as one object a reader can restyle in a single action rather than cell by cell. Keep the per-row form when the ranges are irregular, and the group form when a whole column follows one rule.",[164,2241,2243],{"id":2242},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[2245,2246,2247,2261,2267,2283,2289],"ul",{},[2248,2249,2250,2254,2255,2257,2258,2260],"li",{},[2251,2252,2253],"strong",{},"Off-by-one ranges."," ",[176,2256,776],{}," takes zero-based row and column; the ",[176,2259,768],{}," string is one-based A1 notation.",[2248,2262,2263,2266],{},[2251,2264,2265],{},"Invisible sparklines."," They fill the cell, so a default row height of 15 points makes a line barely visible. Set 18–24.",[2248,2268,2269,2272,2273,1697,2276,2279,2280,2282],{},[2251,2270,2271],{},"Misleading comparisons."," Without ",[176,2274,2275],{},"max",[176,2277,2278],{},"min"," set to ",[176,2281,857],{},", rows scale independently.",[2248,2284,2285,2288],{},[2251,2286,2287],{},"Round-tripping through openpyxl."," Re-saving can drop them; generate the final file with xlsxwriter last.",[2248,2290,2291,2294,2295,2298],{},[2251,2292,2293],{},"Empty cells in the range."," They break the line; fill with zero or use ",[176,2296,2297],{},"show_hidden"," deliberately.",[164,2300,2302],{"id":2301},"performance-and-scale-notes","Performance and scale notes",[10,2304,2305,2306,2310],{},"Sparklines cost almost nothing — they are a small XML extension per group, not an image or a chart object — so a report with hundreds of them stays the same size as one without. The real limit is the reader's: a sheet with a thousand sparklines is slow to scroll in Excel and pointless to look at. Group rows into a summary of a few dozen and put the detail on another sheet. If you need trend visuals in a workbook that must be edited afterwards with openpyxl, generate a small image per row instead, as in ",[14,2307,2309],{"href":2308},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002F","Embed a matplotlib chart in an Excel report"," — heavier, but it survives a round trip.",[164,2312,2314],{"id":2313},"conclusion","Conclusion",[10,2316,2317,2318,2320,2321,2323,2324,2326,2327,2330,2331,2333],{},"One ",[176,2319,776],{}," call per row turns a wide table of monthly figures into something a reader understands without reading a number. Choose ",[176,2322,58],{}," for shape, ",[176,2325,83],{}," for magnitude and ",[176,2328,2329],{},"win_loss"," for variance against target, share the scale with ",[176,2332,857],{}," whenever rows are meant to be compared, and give the rows enough height to see. Because openpyxl cannot add or reliably preserve them, make xlsxwriter the last step that writes the file.",[164,2335,2337],{"id":2336},"frequently-asked-questions","Frequently asked questions",[10,2339,2340,2343],{},[2251,2341,2342],{},"Can openpyxl add sparklines?","\nNot through a supported API. Sparklines are an Excel 2010 extension stored outside the core worksheet schema, so xlsxwriter is the practical route — which means creating a new workbook rather than editing an existing one.",[10,2345,2346,2349,2351,2352,2354,2355,2357],{},[2251,2347,2348],{},"What is the difference between the three types?",[176,2350,58],{}," shows the shape of a trend, ",[176,2353,83],{}," emphasises the size of each period, and ",[176,2356,2329],{}," reduces every value to above or below zero — ideal for variance against target.",[10,2359,2360,2363,2364,2236,2367,2370],{},[2251,2361,2362],{},"Why do two rows with very different magnitudes look identical?","\nEach sparkline scales to its own values by default. Set ",[176,2365,2366],{},"max=\"group\"",[176,2368,2369],{},"min=\"group\""," so a set of rows shares one scale and can be compared honestly.",[10,2372,2373,2376],{},[2251,2374,2375],{},"How do I make them bigger?","\nA sparkline fills its cell, so increase the row height and column width. Around 18 to 24 points of row height reads well for a line sparkline.",[10,2378,2379,2382],{},[2251,2380,2381],{},"Do sparklines survive being read back by openpyxl?","\nRe-saving such a file with openpyxl can drop them, because they live in an extension part it does not preserve. Generate the workbook in one pass with xlsxwriter and do not round-trip it.",[164,2384,2386],{"id":2385},"related","Related",[2245,2388,2389,2397,2404,2411,2418],{},[2248,2390,2391,2254,2394,2396],{},[2251,2392,2393],{},"Up:",[14,2395,17],{"href":16}," — the rest of what this library adds over pandas.",[2248,2398,2399,2403],{},[14,2400,2402],{"href":2401},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","Write a formatted Excel report with xlsxwriter"," — the table these sparklines sit beside.",[2248,2405,2406,2410],{},[14,2407,2409],{"href":2408},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002F","Apply conditional formatting with xlsxwriter"," — data bars and colour scales, the other in-cell visuals.",[2248,2412,2413,2417],{},[14,2414,2416],{"href":2415},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002F","Add a chart to an Excel file with xlsxwriter"," — when a full chart is warranted.",[2248,2419,2420,2424],{},[14,2421,2423],{"href":2422},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python\u002F","Add a summary sheet to an Excel report with Python"," — where a sparkline column earns its place.",[2426,2427,2428],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .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}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":174,"searchDepth":217,"depth":217,"links":2430},[2431,2432,2433,2434,2435,2436,2437,2438,2439,2440,2441,2442,2443],{"id":166,"depth":217,"text":167},{"id":201,"depth":217,"text":202},{"id":780,"depth":217,"text":781},{"id":886,"depth":217,"text":887},{"id":1119,"depth":217,"text":1120},{"id":1250,"depth":217,"text":1251},{"id":1592,"depth":217,"text":1593},{"id":2057,"depth":217,"text":2058},{"id":2242,"depth":217,"text":2243},{"id":2301,"depth":217,"text":2302},{"id":2313,"depth":217,"text":2314},{"id":2336,"depth":217,"text":2337},{"id":2385,"depth":217,"text":2386},"2026-08-27","Put a trend in every row: xlsxwriter sparklines — line, column and win\u002Floss types, high and low markers, shared axes across rows, styling, and the openpyxl caveat.","md",[2448,2450,2452,2454,2456],{"q":2342,"a":2449},"Not through a supported API. Sparklines are an Excel 2010 extension stored outside the core worksheet schema, so xlsxwriter is the practical route — which means creating a new workbook rather than editing an existing one.",{"q":2348,"a":2451},"line shows the shape of a trend, column emphasises the size of each period, and win_loss reduces every value to above or below zero — ideal for variance against target.",{"q":2362,"a":2453},"Each sparkline scales to its own values by default. Set max=\"group\" and min=\"group\" so a set of rows shares one scale and can be compared honestly.",{"q":2375,"a":2455},"A sparkline fills its cell, so increase the row height and column width. Around 18 to 24 points of row height reads well for a line sparkline.",{"q":2381,"a":2457},"Re-saving such a file with openpyxl can drop them, because they live in an extension part it does not preserve. Generate the workbook in one pass with xlsxwriter and do not round-trip it.",{},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter",{"title":2461,"description":2462},"Excel Sparklines with xlsxwriter","Add in-cell sparklines with xlsxwriter: line, column and win_loss types, markers, min\u002Fmax axis sharing, grouped ranges, styling and row-height tips for readable trends.","add-sparklines-to-an-excel-report-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter\u002Findex","how-to","xeMyJJfqSadnKjaACZQ0o4HJZfH8PHXmyAUEkGgUK_Q",[2468,2472],{"title":2469,"path":2470,"stem":2471,"children":-1},"Add a Chart to an Excel File with xlsxwriter","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002Findex",{"title":2473,"path":2474,"stem":2475,"children":-1},"Apply Conditional Formatting with xlsxwriter","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002Findex",1788710159390]