[{"data":1,"prerenderedAt":1902},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl":1868,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl":1877,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl":1882,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl":1883},{"id":4,"title":5,"body":6,"dateModified":1834,"datePublished":1834,"description":1835,"extension":1836,"faq":1837,"keyword":1846,"kind":1847,"meta":1848,"navigation":435,"path":1857,"seo":1858,"slug":1861,"stem":1862,"topics":1863,"type":1866,"__hash__":1867},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl\u002Findex.md","Create a Radar Chart with openpyxl",{"type":7,"value":8,"toc":1820},"minimark",[9,24,29,58,68,387,391,394,628,653,657,660,714,721,840,844,847,1077,1084,1088,1091,1173,1176,1180,1183,1456,1459,1503,1584,1587,1599,1667,1671,1674,1678,1681,1685,1738,1742,1755,1759,1765,1771,1777,1783,1787,1816],[10,11,12,13,17,18,23],"p",{},"A radar chart — also called a spider or web chart — puts each criterion on its own spoke around a circle and draws every option as a shape joining its scores. It is the standard picture for a vendor evaluation, a product comparison or a skills assessment: one glance shows which option is strong where, and whether one dominates the others or they trade off. openpyxl builds it as a native Excel chart. Getting a ",[14,15,16],"em",{},"fair"," picture takes a few settings the defaults do not provide: a scale fixed to the scoring range, axes that Excel actually displays, and distinct colours. This page, part of ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002F","Creating Charts in Excel with openpyxl",", builds one for a three-vendor evaluation.",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," openpyxl",[40,56,57],{"class":50}," pandas\n",[10,59,60,63,64,67],{},[37,61,62],{},"vendor_scores.xlsx"," has a ",[37,65,66],{},"Scores"," sheet with six criteria down the side, a weight for each, and scores from 0 to 5 for three vendors. The chart plots the three score columns; the weights feed the table in Step 5.",[69,70,78,79,78,83,78,87,78,95,78,105,78,110,78,121,78,127,78,133,78,136,78,142,78,146,78,150,78,154,78,158,78,162,78,166,78,170,78,174,78,178,78,183,78,187,78,191,78,193,78,196,78,198,78,201,78,203,78,206,78,208,78,211,78,214,78,218,78,220,78,224,78,226,78,229,78,233,78,237,78,239,78,242,78,244,78,247,78,250,78,253,78,255,78,258,78,260,78,263,78,265,78,267,78,269,78,271,78,273,78,275,78,278,78,281,78,283,78,286,78,288,78,291,78,293,78,295,78,297,78,299,78,301,78,303,78,306,78,309,78,311,78,314,78,316,78,318,78,320,78,322,78,324,78,326,78,328,78,330,78,333,78,337,78,339,78,342,78,344,78,347,78,349,78,351,78,353,78,355,78,357,78,359,78,362,78,366,78,368,78,371,78,373,78,375,78,377,78,379,78,381,78,383,78,385],"svg",{"viewBox":71,"role":72,"ariaLabelledBy":73,"xmlns":76,"style":77},"145 0 469 282","img",[74,75],"rd-data-t","rd-data-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:469px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[80,81,82],"title",{"id":74},"The scoring table",[84,85,86],"desc",{"id":75},"A table of six criteria with weights and scores from 0 to 5 for vendors A, B and C. The three vendor columns are the chart's series and the criteria are its spokes.",[88,89],"rect",{"x":90,"y":91,"width":92,"height":93,"fill":94},"145","0","469","282","#ffffff",[88,96],{"x":97,"y":98,"width":99,"height":100,"rx":101,"fill":102,"stroke":103,"style":104},"161.7","16.0","436.7","30.0","8","#e7ebef","var(--line,#cdd5e6)","stroke-width:1.5px",[88,106],{"x":97,"y":107,"width":99,"height":108,"rx":91,"fill":102,"stroke":103,"style":109},"38.0","8.0","stroke-width:0px",[111,112,114],"g",{"transform":113},"translate(171.7,23.0) scale(0.667)",[115,116],"path",{"d":117,"fill":118,"stroke":119,"style":120},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--teal-ink,#0b6157)","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[122,123,62],"text",{"x":124,"y":125,"style":126},"193.7","36.0","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[88,128],{"x":97,"y":129,"width":130,"height":131,"rx":91,"fill":102,"stroke":103,"style":132},"46.0","32.0","24.0","stroke-width:1px",[88,134],{"x":124,"y":129,"width":135,"height":131,"rx":91,"fill":102,"stroke":103,"style":132},"91.5",[122,137,141],{"x":138,"y":139,"style":140},"239.4","63.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[88,143],{"x":144,"y":129,"width":145,"height":131,"rx":91,"fill":102,"stroke":103,"style":132},"285.2","68.4",[122,147,149],{"x":148,"y":139,"style":140},"319.4","B",[88,151],{"x":152,"y":129,"width":153,"height":131,"rx":91,"fill":102,"stroke":103,"style":132},"353.6","82.0",[122,155,157],{"x":156,"y":139,"style":140},"394.6","C",[88,159],{"x":160,"y":129,"width":161,"height":131,"rx":91,"fill":102,"stroke":103,"style":132},"435.6","80.9",[122,163,165],{"x":164,"y":139,"style":140},"476.0","D",[88,167],{"x":168,"y":129,"width":169,"height":131,"rx":91,"fill":102,"stroke":103,"style":132},"516.5","81.9",[122,171,173],{"x":172,"y":139,"style":140},"557.4","E",[88,175],{"x":97,"y":176,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"70.0","28.0",[122,179,182],{"x":180,"y":181,"style":140},"177.7","89.0","1",[88,184],{"x":124,"y":176,"width":135,"height":177,"rx":91,"fill":185,"stroke":186,"style":104},"#f0f4ff","var(--brand,#5b5cf0)",[122,188,190],{"x":138,"y":181,"style":189},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Criterion",[88,192],{"x":144,"y":176,"width":145,"height":177,"rx":91,"fill":185,"stroke":186,"style":104},[122,194,195],{"x":148,"y":181,"style":189},"Weight",[88,197],{"x":152,"y":176,"width":153,"height":177,"rx":91,"fill":185,"stroke":186,"style":104},[122,199,200],{"x":156,"y":181,"style":189},"Vendor A",[88,202],{"x":160,"y":176,"width":161,"height":177,"rx":91,"fill":185,"stroke":186,"style":104},[122,204,205],{"x":164,"y":181,"style":189},"Vendor B",[88,207],{"x":168,"y":176,"width":169,"height":177,"rx":91,"fill":185,"stroke":186,"style":104},[122,209,210],{"x":172,"y":181,"style":189},"Vendor C",[88,212],{"x":97,"y":213,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"98.0",[122,215,217],{"x":180,"y":216,"style":140},"117.0","2",[88,219],{"x":124,"y":213,"width":135,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,221,223],{"x":138,"y":216,"style":222},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Price",[88,225],{"x":144,"y":213,"width":145,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,227,228],{"x":148,"y":216,"style":222},"0.25",[88,230],{"x":152,"y":213,"width":153,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},"#d9f4f1","var(--teal,#0f9488)",[122,234,236],{"x":156,"y":216,"style":235},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","4",[88,238],{"x":160,"y":213,"width":161,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,240,241],{"x":164,"y":216,"style":235},"3",[88,243],{"x":168,"y":213,"width":169,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,245,246],{"x":172,"y":216,"style":235},"5",[88,248],{"x":97,"y":249,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"126.0",[122,251,241],{"x":180,"y":252,"style":140},"145.0",[88,254],{"x":124,"y":249,"width":135,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,256,257],{"x":138,"y":252,"style":222},"Features",[88,259],{"x":144,"y":249,"width":145,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,261,262],{"x":148,"y":252,"style":222},"0.20",[88,264],{"x":152,"y":249,"width":153,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,266,246],{"x":156,"y":252,"style":235},[88,268],{"x":160,"y":249,"width":161,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,270,236],{"x":164,"y":252,"style":235},[88,272],{"x":168,"y":249,"width":169,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,274,217],{"x":172,"y":252,"style":235},[88,276],{"x":97,"y":277,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"154.0",[122,279,236],{"x":180,"y":280,"style":140},"173.0",[88,282],{"x":124,"y":277,"width":135,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,284,285],{"x":138,"y":280,"style":222},"Support",[88,287],{"x":144,"y":277,"width":145,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,289,290],{"x":148,"y":280,"style":222},"0.15",[88,292],{"x":152,"y":277,"width":153,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,294,241],{"x":156,"y":280,"style":235},[88,296],{"x":160,"y":277,"width":161,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,298,246],{"x":164,"y":280,"style":235},[88,300],{"x":168,"y":277,"width":169,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,302,241],{"x":172,"y":280,"style":235},[88,304],{"x":97,"y":305,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"182.0",[122,307,246],{"x":180,"y":308,"style":140},"201.0",[88,310],{"x":124,"y":305,"width":135,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,312,313],{"x":138,"y":308,"style":222},"Security",[88,315],{"x":144,"y":305,"width":145,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,317,262],{"x":148,"y":308,"style":222},[88,319],{"x":152,"y":305,"width":153,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,321,236],{"x":156,"y":308,"style":235},[88,323],{"x":160,"y":305,"width":161,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,325,236],{"x":164,"y":308,"style":235},[88,327],{"x":168,"y":305,"width":169,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,329,241],{"x":172,"y":308,"style":235},[88,331],{"x":97,"y":332,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"210.0",[122,334,336],{"x":180,"y":335,"style":140},"229.0","6",[88,338],{"x":124,"y":332,"width":135,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,340,341],{"x":138,"y":335,"style":222},"Integration",[88,343],{"x":144,"y":332,"width":145,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,345,346],{"x":148,"y":335,"style":222},"0.10",[88,348],{"x":152,"y":332,"width":153,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,350,246],{"x":156,"y":335,"style":235},[88,352],{"x":160,"y":332,"width":161,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,354,217],{"x":164,"y":335,"style":235},[88,356],{"x":168,"y":332,"width":169,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,358,241],{"x":172,"y":335,"style":235},[88,360],{"x":97,"y":361,"width":130,"height":177,"rx":91,"fill":102,"stroke":103,"style":132},"238.0",[122,363,365],{"x":180,"y":364,"style":140},"257.0","7",[88,367],{"x":124,"y":361,"width":135,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,369,370],{"x":138,"y":364,"style":222},"Usability",[88,372],{"x":144,"y":361,"width":145,"height":177,"rx":91,"fill":94,"stroke":103,"style":132},[122,374,346],{"x":148,"y":364,"style":222},[88,376],{"x":152,"y":361,"width":153,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,378,241],{"x":156,"y":364,"style":235},[88,380],{"x":160,"y":361,"width":161,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,382,236],{"x":164,"y":364,"style":235},[88,384],{"x":168,"y":361,"width":169,"height":177,"rx":91,"fill":231,"stroke":232,"style":104},[122,386,236],{"x":172,"y":364,"style":235},[25,388,390],{"id":389},"step-1-build-the-chart-from-the-table","Step 1 — Build the chart from the table",[10,392,393],{},"Each vendor column is a series and the criteria are the categories, which become the spokes:",[30,395,399],{"className":396,"code":397,"language":398,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\nfrom openpyxl.chart import RadarChart, Reference\n\nwb = load_workbook(\"vendor_scores.xlsx\")\nws = wb[\"Scores\"]\nchart = RadarChart()\nchart.type = \"marker\"\nchart.title = \"Vendor evaluation (0–5)\"\nchart.add_data(Reference(ws, min_col=3, max_col=5, min_row=1, max_row=7), titles_from_data=True)\nchart.set_categories(Reference(ws, min_col=1, min_row=2, max_row=7))\nchart.width, chart.height = 14, 10\nprint(len(chart.series), \"series\")\n","python",[37,400,401,417,430,437,455,472,483,494,505,561,592,608],{"__ignoreMap":35},[40,402,403,407,411,414],{"class":42,"line":43},[40,404,406],{"class":405},"s-kum","from",[40,408,410],{"class":409},"skGVy"," openpyxl ",[40,412,413],{"class":405},"import",[40,415,416],{"class":409}," load_workbook\n",[40,418,420,422,425,427],{"class":42,"line":419},2,[40,421,406],{"class":405},[40,423,424],{"class":409}," openpyxl.chart ",[40,426,413],{"class":405},[40,428,429],{"class":409}," RadarChart, Reference\n",[40,431,433],{"class":42,"line":432},3,[40,434,436],{"emptyLinePlaceholder":435},true,"\n",[40,438,440,443,446,449,452],{"class":42,"line":439},4,[40,441,442],{"class":409},"wb ",[40,444,445],{"class":405},"=",[40,447,448],{"class":409}," load_workbook(",[40,450,451],{"class":50},"\"vendor_scores.xlsx\"",[40,453,454],{"class":409},")\n",[40,456,458,461,463,466,469],{"class":42,"line":457},5,[40,459,460],{"class":409},"ws ",[40,462,445],{"class":405},[40,464,465],{"class":409}," wb[",[40,467,468],{"class":50},"\"Scores\"",[40,470,471],{"class":409},"]\n",[40,473,475,478,480],{"class":42,"line":474},6,[40,476,477],{"class":409},"chart ",[40,479,445],{"class":405},[40,481,482],{"class":409}," RadarChart()\n",[40,484,486,489,491],{"class":42,"line":485},7,[40,487,488],{"class":409},"chart.type ",[40,490,445],{"class":405},[40,492,493],{"class":50}," \"marker\"\n",[40,495,497,500,502],{"class":42,"line":496},8,[40,498,499],{"class":409},"chart.title ",[40,501,445],{"class":405},[40,503,504],{"class":50}," \"Vendor evaluation (0–5)\"\n",[40,506,508,511,515,517,520,523,526,528,530,532,535,537,539,541,544,546,548,551,554,556,559],{"class":42,"line":507},9,[40,509,510],{"class":409},"chart.add_data(Reference(ws, ",[40,512,514],{"class":513},"sa561","min_col",[40,516,445],{"class":405},[40,518,241],{"class":519},"sP0c6",[40,521,522],{"class":409},", ",[40,524,525],{"class":513},"max_col",[40,527,445],{"class":405},[40,529,246],{"class":519},[40,531,522],{"class":409},[40,533,534],{"class":513},"min_row",[40,536,445],{"class":405},[40,538,182],{"class":519},[40,540,522],{"class":409},[40,542,543],{"class":513},"max_row",[40,545,445],{"class":405},[40,547,365],{"class":519},[40,549,550],{"class":409},"), ",[40,552,553],{"class":513},"titles_from_data",[40,555,445],{"class":405},[40,557,558],{"class":519},"True",[40,560,454],{"class":409},[40,562,564,567,569,571,573,575,577,579,581,583,585,587,589],{"class":42,"line":563},10,[40,565,566],{"class":409},"chart.set_categories(Reference(ws, ",[40,568,514],{"class":513},[40,570,445],{"class":405},[40,572,182],{"class":519},[40,574,522],{"class":409},[40,576,534],{"class":513},[40,578,445],{"class":405},[40,580,217],{"class":519},[40,582,522],{"class":409},[40,584,543],{"class":513},[40,586,445],{"class":405},[40,588,365],{"class":519},[40,590,591],{"class":409},"))\n",[40,593,595,598,600,603,605],{"class":42,"line":594},11,[40,596,597],{"class":409},"chart.width, chart.height ",[40,599,445],{"class":405},[40,601,602],{"class":519}," 14",[40,604,522],{"class":409},[40,606,607],{"class":519},"10\n",[40,609,611,614,617,620,623,626],{"class":42,"line":610},12,[40,612,613],{"class":519},"print",[40,615,616],{"class":409},"(",[40,618,619],{"class":519},"len",[40,621,622],{"class":409},"(chart.series), ",[40,624,625],{"class":50},"\"series\"",[40,627,454],{"class":409},[10,629,630,633,634,637,638,641,642,645,646,649,650,652],{},[37,631,632],{},"titles_from_data=True"," takes each series name from the header row, so the legend says \"Vendor A\" rather than \"Series1\". ",[37,635,636],{},"type"," chooses the style: ",[37,639,640],{},"\"standard\""," draws lines, ",[37,643,644],{},"\"marker\""," adds a dot at each score, and ",[37,647,648],{},"\"filled\""," fills each shape. With several series, filled shapes hide each other — the last series drawn covers the earlier ones — so ",[37,651,644],{}," is the better default for comparisons.",[25,654,656],{"id":655},"step-2-fix-the-scale-and-show-the-axes","Step 2 — Fix the scale and show the axes",[10,658,659],{},"Left alone, Excel scales the value axis to the data, so if every score happens to be 2 or more, the centre of the chart is 2, not 0, and differences look larger than they are. Fix the axis to the scoring range, and tell Excel to display the axes — openpyxl writes them marked as deleted by default, which Excel honours and LibreOffice ignores:",[30,661,663],{"className":396,"code":662,"language":398,"meta":35,"style":35},"chart.y_axis.scaling.min = 0\nchart.y_axis.scaling.max = 5\nchart.y_axis.majorUnit = 1\nchart.y_axis.delete = False\nchart.x_axis.delete = False\n",[37,664,665,675,685,695,705],{"__ignoreMap":35},[40,666,667,670,672],{"class":42,"line":43},[40,668,669],{"class":409},"chart.y_axis.scaling.min ",[40,671,445],{"class":405},[40,673,674],{"class":519}," 0\n",[40,676,677,680,682],{"class":42,"line":419},[40,678,679],{"class":409},"chart.y_axis.scaling.max ",[40,681,445],{"class":405},[40,683,684],{"class":519}," 5\n",[40,686,687,690,692],{"class":42,"line":432},[40,688,689],{"class":409},"chart.y_axis.majorUnit ",[40,691,445],{"class":405},[40,693,694],{"class":519}," 1\n",[40,696,697,700,702],{"class":42,"line":439},[40,698,699],{"class":409},"chart.y_axis.delete ",[40,701,445],{"class":405},[40,703,704],{"class":519}," False\n",[40,706,707,710,712],{"class":42,"line":457},[40,708,709],{"class":409},"chart.x_axis.delete ",[40,711,445],{"class":405},[40,713,704],{"class":519},[10,715,716,717,720],{},"A fixed 0–5 scale means a vendor scoring 5 everywhere draws the outer ring and a vendor scoring 0 a dot at the centre — the picture means the same thing in every evaluation, which matters when people compare charts from different reports. The ",[37,718,719],{},"delete = False"," lines are the difference between a chart with labelled spokes and gridline values in Excel and a floating shape with no reference.",[69,722,78,728,78,731,78,734,78,738,78,747,78,755,78,761,78,765,78,771,78,779,78,783,78,788,78,792,78,797,78,800,78,806,78,811,78,815,78,819,78,824,78,827,78,832,78,835],{"viewBox":723,"role":72,"ariaLabelledBy":724,"xmlns":76,"style":727},"4 0 752 207",[725,726],"rd-scale-t","rd-scale-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[80,729,730],{"id":725},"Auto scale and fixed scale",[84,732,733],{"id":726},"Two panels. With an automatic scale the centre of the chart is set by the data and small differences look large. With the scale fixed to 0 to 5 the centre is always zero and shapes are comparable between charts.",[88,735],{"x":236,"y":91,"width":736,"height":737,"fill":94},"752","207",[88,739],{"x":740,"y":98,"width":741,"height":742,"rx":743,"fill":744,"stroke":745,"style":746},"20.0","351.0","175.0","14","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[111,748,750],{"transform":749},"translate(182.5,29.0) scale(1.083)",[115,751],{"d":752,"fill":118,"stroke":753,"style":754},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[122,756,760],{"x":757,"y":758,"style":759},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Automatic scale",[42,762],{"x1":125,"y1":763,"x2":764,"y2":763,"stroke":745,"style":132},"91.0","355.0",[122,766,770],{"x":767,"y":768,"style":769},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","centre moves with the data",[111,772,774],{"transform":773},"translate(38.5,99.5) scale(0.625)",[115,775],{"d":776,"fill":118,"stroke":777,"style":778},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[122,780,782],{"x":767,"y":781,"style":769},"137.0","small gaps look dramatic",[111,784,786],{"transform":785},"translate(38.5,125.5) scale(0.625)",[115,787],{"d":776,"fill":118,"stroke":777,"style":778},[122,789,791],{"x":767,"y":790,"style":769},"163.0","charts not comparable",[111,793,795],{"transform":794},"translate(38.5,151.5) scale(0.625)",[115,796],{"d":776,"fill":118,"stroke":777,"style":778},[88,798],{"x":799,"y":98,"width":741,"height":742,"rx":743,"fill":231,"stroke":232,"style":746},"389.0",[111,801,803],{"transform":802},"translate(551.5,29.0) scale(1.083)",[115,804],{"d":805,"fill":118,"stroke":119,"style":754},"M5 12.5l4.5 4.5L19 7",[122,807,810],{"x":808,"y":758,"style":809},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Fixed 0–5",[42,812],{"x1":813,"y1":763,"x2":814,"y2":763,"stroke":232,"style":132},"405.0","724.0",[122,816,818],{"x":817,"y":768,"style":769},"429.0","centre is always 0",[111,820,822],{"transform":821},"translate(407.5,99.5) scale(0.625)",[115,823],{"d":805,"fill":118,"stroke":232,"style":778},[122,825,826],{"x":817,"y":781,"style":769},"outer ring is always 5",[111,828,830],{"transform":829},"translate(407.5,125.5) scale(0.625)",[115,831],{"d":805,"fill":118,"stroke":232,"style":778},[122,833,834],{"x":817,"y":790,"style":769},"same meaning in every report",[111,836,838],{"transform":837},"translate(407.5,151.5) scale(0.625)",[115,839],{"d":805,"fill":118,"stroke":232,"style":778},[25,841,843],{"id":842},"step-3-one-colour-and-marker-per-vendor","Step 3 — One colour and marker per vendor",[10,845,846],{},"Give each series a distinct line colour, a slightly thicker line and matching markers, so vendors stay distinguishable in print and for colour-blind readers:",[30,848,850],{"className":396,"code":849,"language":398,"meta":35,"style":35},"COLOURS = {\"Vendor A\": \"4338CA\", \"Vendor B\": \"0F9488\", \"Vendor C\": \"B4740A\"}\nSYMBOLS = {\"Vendor A\": \"circle\", \"Vendor B\": \"square\", \"Vendor C\": \"triangle\"}\n\nfor series, name in zip(chart.series, COLOURS):\n    colour = COLOURS[name]\n    series.graphicalProperties.line.solidFill = colour\n    series.graphicalProperties.line.width = 28575          # 2.25 pt, in EMU\n    series.marker.symbol = SYMBOLS[name]\n    series.marker.size = 7\n    series.marker.graphicalProperties.solidFill = colour\n    series.marker.graphicalProperties.line.solidFill = colour\n\nws.add_chart(chart, \"A10\")\nwb.save(\"vendor_scores.xlsx\")\nprint([s.marker.symbol for s in chart.series])\n",[37,851,852,895,931,935,957,970,980,994,1006,1016,1025,1034,1038,1049,1059],{"__ignoreMap":35},[40,853,854,857,860,863,866,869,872,874,877,879,882,884,887,889,892],{"class":42,"line":43},[40,855,856],{"class":519},"COLOURS",[40,858,859],{"class":405}," =",[40,861,862],{"class":409}," {",[40,864,865],{"class":50},"\"Vendor A\"",[40,867,868],{"class":409},": ",[40,870,871],{"class":50},"\"4338CA\"",[40,873,522],{"class":409},[40,875,876],{"class":50},"\"Vendor B\"",[40,878,868],{"class":409},[40,880,881],{"class":50},"\"0F9488\"",[40,883,522],{"class":409},[40,885,886],{"class":50},"\"Vendor C\"",[40,888,868],{"class":409},[40,890,891],{"class":50},"\"B4740A\"",[40,893,894],{"class":409},"}\n",[40,896,897,900,902,904,906,908,911,913,915,917,920,922,924,926,929],{"class":42,"line":419},[40,898,899],{"class":519},"SYMBOLS",[40,901,859],{"class":405},[40,903,862],{"class":409},[40,905,865],{"class":50},[40,907,868],{"class":409},[40,909,910],{"class":50},"\"circle\"",[40,912,522],{"class":409},[40,914,876],{"class":50},[40,916,868],{"class":409},[40,918,919],{"class":50},"\"square\"",[40,921,522],{"class":409},[40,923,886],{"class":50},[40,925,868],{"class":409},[40,927,928],{"class":50},"\"triangle\"",[40,930,894],{"class":409},[40,932,933],{"class":42,"line":432},[40,934,436],{"emptyLinePlaceholder":435},[40,936,937,940,943,946,949,952,954],{"class":42,"line":439},[40,938,939],{"class":405},"for",[40,941,942],{"class":409}," series, name ",[40,944,945],{"class":405},"in",[40,947,948],{"class":519}," zip",[40,950,951],{"class":409},"(chart.series, ",[40,953,856],{"class":519},[40,955,956],{"class":409},"):\n",[40,958,959,962,964,967],{"class":42,"line":457},[40,960,961],{"class":409},"    colour ",[40,963,445],{"class":405},[40,965,966],{"class":519}," COLOURS",[40,968,969],{"class":409},"[name]\n",[40,971,972,975,977],{"class":42,"line":474},[40,973,974],{"class":409},"    series.graphicalProperties.line.solidFill ",[40,976,445],{"class":405},[40,978,979],{"class":409}," colour\n",[40,981,982,985,987,990],{"class":42,"line":485},[40,983,984],{"class":409},"    series.graphicalProperties.line.width ",[40,986,445],{"class":405},[40,988,989],{"class":519}," 28575",[40,991,993],{"class":992},"s-wDw","          # 2.25 pt, in EMU\n",[40,995,996,999,1001,1004],{"class":42,"line":496},[40,997,998],{"class":409},"    series.marker.symbol ",[40,1000,445],{"class":405},[40,1002,1003],{"class":519}," SYMBOLS",[40,1005,969],{"class":409},[40,1007,1008,1011,1013],{"class":42,"line":507},[40,1009,1010],{"class":409},"    series.marker.size ",[40,1012,445],{"class":405},[40,1014,1015],{"class":519}," 7\n",[40,1017,1018,1021,1023],{"class":42,"line":563},[40,1019,1020],{"class":409},"    series.marker.graphicalProperties.solidFill ",[40,1022,445],{"class":405},[40,1024,979],{"class":409},[40,1026,1027,1030,1032],{"class":42,"line":594},[40,1028,1029],{"class":409},"    series.marker.graphicalProperties.line.solidFill ",[40,1031,445],{"class":405},[40,1033,979],{"class":409},[40,1035,1036],{"class":42,"line":610},[40,1037,436],{"emptyLinePlaceholder":435},[40,1039,1041,1044,1047],{"class":42,"line":1040},13,[40,1042,1043],{"class":409},"ws.add_chart(chart, ",[40,1045,1046],{"class":50},"\"A10\"",[40,1048,454],{"class":409},[40,1050,1052,1055,1057],{"class":42,"line":1051},14,[40,1053,1054],{"class":409},"wb.save(",[40,1056,451],{"class":50},[40,1058,454],{"class":409},[40,1060,1062,1064,1067,1069,1072,1074],{"class":42,"line":1061},15,[40,1063,613],{"class":519},[40,1065,1066],{"class":409},"([s.marker.symbol ",[40,1068,939],{"class":405},[40,1070,1071],{"class":409}," s ",[40,1073,945],{"class":405},[40,1075,1076],{"class":409}," chart.series])\n",[10,1078,1079,1080,1083],{},"Line widths are in EMU (English Metric Units): 12,700 per point. Different marker shapes as well as different colours keep the series apart when the chart is printed in grey. Anchoring the chart at ",[37,1081,1082],{},"A10",", below the table, keeps it within a printed page's width.",[25,1085,1087],{"id":1086},"step-4-check-the-rendering","Step 4 — Check the rendering",[10,1089,1090],{},"Convert the workbook to PDF with LibreOffice and look at the chart:",[30,1092,1094],{"className":32,"code":1093,"language":34,"meta":35,"style":35},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to pdf vendor_scores.xlsx > \u002Fdev\u002Fnull 2>&1\npdftoppm -png -r 60 -f 1 -l 1 vendor_scores.pdf preview && ls preview*\n",[37,1095,1096,1131],{"__ignoreMap":35},[40,1097,1098,1101,1104,1107,1110,1113,1116,1119,1122,1125,1128],{"class":42,"line":43},[40,1099,1100],{"class":46},"soffice",[40,1102,1103],{"class":519}," -env:UserInstallation=file:\u002F\u002F",[40,1105,1106],{"class":409},"$PWD",[40,1108,1109],{"class":519},"\u002Flo_profile",[40,1111,1112],{"class":519}," --headless",[40,1114,1115],{"class":519}," --convert-to",[40,1117,1118],{"class":50}," pdf",[40,1120,1121],{"class":50}," vendor_scores.xlsx",[40,1123,1124],{"class":405}," >",[40,1126,1127],{"class":50}," \u002Fdev\u002Fnull",[40,1129,1130],{"class":405}," 2>&1\n",[40,1132,1133,1136,1139,1142,1145,1148,1151,1154,1156,1159,1162,1165,1168,1170],{"class":42,"line":419},[40,1134,1135],{"class":46},"pdftoppm",[40,1137,1138],{"class":519}," -png",[40,1140,1141],{"class":519}," -r",[40,1143,1144],{"class":519}," 60",[40,1146,1147],{"class":519}," -f",[40,1149,1150],{"class":519}," 1",[40,1152,1153],{"class":519}," -l",[40,1155,1150],{"class":519},[40,1157,1158],{"class":50}," vendor_scores.pdf",[40,1160,1161],{"class":50}," preview",[40,1163,1164],{"class":409}," && ",[40,1166,1167],{"class":46},"ls",[40,1169,1161],{"class":50},[40,1171,1172],{"class":519},"*\n",[10,1174,1175],{},"The rendering shows the trade-offs directly. Vendor A reaches the outer ring on features and integration; Vendor B is strongest on support and weakest on integration; Vendor C wins on price and loses on features. No vendor encloses the others — which is exactly why the next step is needed.",[25,1177,1179],{"id":1178},"step-5-add-a-weighted-score-next-to-the-chart","Step 5 — Add a weighted score next to the chart",[10,1181,1182],{},"A radar chart shows shapes, and people read area as quality — but area depends on the order of the spokes, and every criterion counts equally in it even when the weights say otherwise. Put the weighted total next to the chart, as the number the decision rests on:",[30,1184,1186],{"className":396,"code":1185,"language":398,"meta":35,"style":35},"import pandas as pd\n\nscores = pd.read_excel(\"vendor_scores.xlsx\", sheet_name=\"Scores\")\nvendors = [\"Vendor A\", \"Vendor B\", \"Vendor C\"]\nweighted = (scores[vendors].mul(scores[\"Weight\"], axis=0)).sum().round(2)\nranking = weighted.sort_values(ascending=False).rename(\"Weighted score\").to_frame()\nprint(ranking)\n\nwb = load_workbook(\"vendor_scores.xlsx\")\nws = wb[\"Scores\"]\nws[\"H1\"], ws[\"I1\"] = \"Vendor\", \"Weighted score (0–5)\"\nfor i, (vendor, row) in enumerate(ranking.iterrows(), start=2):\n    ws.cell(i, 8, vendor)\n    ws.cell(i, 9, f\"=SUMPRODUCT($B$2:$B$7,{'CDE'[vendors.index(vendor)]}2:{'CDE'[vendors.index(vendor)]}7)\")\nwb.save(\"vendor_scores_ranked.xlsx\")\n",[37,1187,1188,1201,1205,1228,1250,1280,1307,1314,1318,1330,1342,1369,1393,1403,1447],{"__ignoreMap":35},[40,1189,1190,1192,1195,1198],{"class":42,"line":43},[40,1191,413],{"class":405},[40,1193,1194],{"class":409}," pandas ",[40,1196,1197],{"class":405},"as",[40,1199,1200],{"class":409}," pd\n",[40,1202,1203],{"class":42,"line":419},[40,1204,436],{"emptyLinePlaceholder":435},[40,1206,1207,1210,1212,1215,1217,1219,1222,1224,1226],{"class":42,"line":432},[40,1208,1209],{"class":409},"scores ",[40,1211,445],{"class":405},[40,1213,1214],{"class":409}," pd.read_excel(",[40,1216,451],{"class":50},[40,1218,522],{"class":409},[40,1220,1221],{"class":513},"sheet_name",[40,1223,445],{"class":405},[40,1225,468],{"class":50},[40,1227,454],{"class":409},[40,1229,1230,1233,1235,1238,1240,1242,1244,1246,1248],{"class":42,"line":439},[40,1231,1232],{"class":409},"vendors ",[40,1234,445],{"class":405},[40,1236,1237],{"class":409}," [",[40,1239,865],{"class":50},[40,1241,522],{"class":409},[40,1243,876],{"class":50},[40,1245,522],{"class":409},[40,1247,886],{"class":50},[40,1249,471],{"class":409},[40,1251,1252,1255,1257,1260,1263,1266,1269,1271,1273,1276,1278],{"class":42,"line":457},[40,1253,1254],{"class":409},"weighted ",[40,1256,445],{"class":405},[40,1258,1259],{"class":409}," (scores[vendors].mul(scores[",[40,1261,1262],{"class":50},"\"Weight\"",[40,1264,1265],{"class":409},"], ",[40,1267,1268],{"class":513},"axis",[40,1270,445],{"class":405},[40,1272,91],{"class":519},[40,1274,1275],{"class":409},")).sum().round(",[40,1277,217],{"class":519},[40,1279,454],{"class":409},[40,1281,1282,1285,1287,1290,1293,1295,1298,1301,1304],{"class":42,"line":474},[40,1283,1284],{"class":409},"ranking ",[40,1286,445],{"class":405},[40,1288,1289],{"class":409}," weighted.sort_values(",[40,1291,1292],{"class":513},"ascending",[40,1294,445],{"class":405},[40,1296,1297],{"class":519},"False",[40,1299,1300],{"class":409},").rename(",[40,1302,1303],{"class":50},"\"Weighted score\"",[40,1305,1306],{"class":409},").to_frame()\n",[40,1308,1309,1311],{"class":42,"line":485},[40,1310,613],{"class":519},[40,1312,1313],{"class":409},"(ranking)\n",[40,1315,1316],{"class":42,"line":496},[40,1317,436],{"emptyLinePlaceholder":435},[40,1319,1320,1322,1324,1326,1328],{"class":42,"line":507},[40,1321,442],{"class":409},[40,1323,445],{"class":405},[40,1325,448],{"class":409},[40,1327,451],{"class":50},[40,1329,454],{"class":409},[40,1331,1332,1334,1336,1338,1340],{"class":42,"line":563},[40,1333,460],{"class":409},[40,1335,445],{"class":405},[40,1337,465],{"class":409},[40,1339,468],{"class":50},[40,1341,471],{"class":409},[40,1343,1344,1347,1350,1353,1356,1359,1361,1364,1366],{"class":42,"line":594},[40,1345,1346],{"class":409},"ws[",[40,1348,1349],{"class":50},"\"H1\"",[40,1351,1352],{"class":409},"], ws[",[40,1354,1355],{"class":50},"\"I1\"",[40,1357,1358],{"class":409},"] ",[40,1360,445],{"class":405},[40,1362,1363],{"class":50}," \"Vendor\"",[40,1365,522],{"class":409},[40,1367,1368],{"class":50},"\"Weighted score (0–5)\"\n",[40,1370,1371,1373,1376,1378,1381,1384,1387,1389,1391],{"class":42,"line":610},[40,1372,939],{"class":405},[40,1374,1375],{"class":409}," i, (vendor, row) ",[40,1377,945],{"class":405},[40,1379,1380],{"class":519}," enumerate",[40,1382,1383],{"class":409},"(ranking.iterrows(), ",[40,1385,1386],{"class":513},"start",[40,1388,445],{"class":405},[40,1390,217],{"class":519},[40,1392,956],{"class":409},[40,1394,1395,1398,1400],{"class":42,"line":1040},[40,1396,1397],{"class":409},"    ws.cell(i, ",[40,1399,101],{"class":519},[40,1401,1402],{"class":409},", vendor)\n",[40,1404,1405,1407,1410,1412,1415,1418,1422,1425,1428,1431,1434,1436,1438,1440,1442,1445],{"class":42,"line":1051},[40,1406,1397],{"class":409},[40,1408,1409],{"class":519},"9",[40,1411,522],{"class":409},[40,1413,1414],{"class":405},"f",[40,1416,1417],{"class":50},"\"=SUMPRODUCT($B$2:$B$7,",[40,1419,1421],{"class":1420},"sSjpA","{",[40,1423,1424],{"class":50},"'CDE'",[40,1426,1427],{"class":409},"[vendors.index(vendor)]",[40,1429,1430],{"class":1420},"}",[40,1432,1433],{"class":50},"2:",[40,1435,1421],{"class":1420},[40,1437,1424],{"class":50},[40,1439,1427],{"class":409},[40,1441,1430],{"class":1420},[40,1443,1444],{"class":50},"7)\"",[40,1446,454],{"class":409},[40,1448,1449,1451,1454],{"class":42,"line":1061},[40,1450,1054],{"class":409},[40,1452,1453],{"class":50},"\"vendor_scores_ranked.xlsx\"",[40,1455,454],{"class":409},[10,1457,1458],{},"Check that the formulas agree with pandas by letting LibreOffice calculate a copy:",[30,1460,1462],{"className":32,"code":1461,"language":34,"meta":35,"style":35},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to xlsx --outdir recalculated vendor_scores_ranked.xlsx > \u002Fdev\u002Fnull 2>&1\nls recalculated\n",[37,1463,1464,1496],{"__ignoreMap":35},[40,1465,1466,1468,1470,1472,1474,1476,1478,1481,1484,1487,1490,1492,1494],{"class":42,"line":43},[40,1467,1100],{"class":46},[40,1469,1103],{"class":519},[40,1471,1106],{"class":409},[40,1473,1109],{"class":519},[40,1475,1112],{"class":519},[40,1477,1115],{"class":519},[40,1479,1480],{"class":50}," xlsx",[40,1482,1483],{"class":519}," --outdir",[40,1485,1486],{"class":50}," recalculated",[40,1488,1489],{"class":50}," vendor_scores_ranked.xlsx",[40,1491,1124],{"class":405},[40,1493,1127],{"class":50},[40,1495,1130],{"class":405},[40,1497,1498,1500],{"class":42,"line":419},[40,1499,1167],{"class":46},[40,1501,1502],{"class":50}," recalculated\n",[30,1504,1506],{"className":396,"code":1505,"language":398,"meta":35,"style":35},"check = load_workbook(\"recalculated\u002Fvendor_scores_ranked.xlsx\", data_only=True)[\"Scores\"]\nprint([(check.cell(r, 8).value, round(check.cell(r, 9).value, 2)) for r in range(2, 5)])\n",[37,1507,1508,1536],{"__ignoreMap":35},[40,1509,1510,1513,1515,1517,1520,1522,1525,1527,1529,1532,1534],{"class":42,"line":43},[40,1511,1512],{"class":409},"check ",[40,1514,445],{"class":405},[40,1516,448],{"class":409},[40,1518,1519],{"class":50},"\"recalculated\u002Fvendor_scores_ranked.xlsx\"",[40,1521,522],{"class":409},[40,1523,1524],{"class":513},"data_only",[40,1526,445],{"class":405},[40,1528,558],{"class":519},[40,1530,1531],{"class":409},")[",[40,1533,468],{"class":50},[40,1535,471],{"class":409},[40,1537,1538,1540,1543,1545,1548,1551,1554,1556,1558,1560,1563,1565,1568,1570,1573,1575,1577,1579,1581],{"class":42,"line":419},[40,1539,613],{"class":519},[40,1541,1542],{"class":409},"([(check.cell(r, ",[40,1544,101],{"class":519},[40,1546,1547],{"class":409},").value, ",[40,1549,1550],{"class":519},"round",[40,1552,1553],{"class":409},"(check.cell(r, ",[40,1555,1409],{"class":519},[40,1557,1547],{"class":409},[40,1559,217],{"class":519},[40,1561,1562],{"class":409},")) ",[40,1564,939],{"class":405},[40,1566,1567],{"class":409}," r ",[40,1569,945],{"class":405},[40,1571,1572],{"class":519}," range",[40,1574,616],{"class":409},[40,1576,217],{"class":519},[40,1578,522],{"class":409},[40,1580,246],{"class":519},[40,1582,1583],{"class":409},")])\n",[10,1585,1586],{},"The formulas give the same 4.05, 3.70 and 3.40, in the same order. Vendor A leads because it is strong on the two heaviest criteria, price and features, while Vendor B's best score is on support, which carries less weight — something the radar shapes alone do not reveal.",[10,1588,1589,1590,1593,1594,1598],{},"The ranking is computed in pandas to sort it, and written back as ",[37,1591,1592],{},"SUMPRODUCT"," formulas so a reviewer can change a weight on the sheet and see the scores update. Reopening a workbook with openpyxl drops its existing charts, so this version is saved under a new name; in a real script, build the chart and the table in the same run before saving. ",[19,1595,1597],{"href":1596},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumproduct-and-weighted-average-in-pandas\u002F","SUMPRODUCT and Weighted Average in pandas"," covers the calculation.",[69,1600,78,1606,78,1609,78,1612,78,1616,78,1619,78,1624,78,1628,78,1633,78,1636,78,1639,78,1642,78,1647,78,1650,78,1653,78,1656,78,1661],{"viewBox":1601,"role":72,"ariaLabelledBy":1602,"xmlns":76,"style":1605},"4 0 744 166",[1603,1604],"rd-weighted-t","rd-weighted-d","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[80,1607,1608],{"id":1603},"Weighted scores out of 5",[84,1610,1611],{"id":1604},"Horizontal bars of the weighted scores. Vendor A scores 4.05, Vendor B 3.70 and Vendor C 3.40.",[88,1613],{"x":236,"y":91,"width":1614,"height":1615,"fill":94},"744","166",[88,1617],{"x":740,"y":98,"width":1618,"height":177,"rx":365,"fill":102,"stroke":103,"style":132},"88.4",[122,1620,200],{"x":1621,"y":1622,"style":1623},"64.2","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[88,1625],{"x":1626,"y":98,"width":1627,"height":177,"rx":365,"fill":185,"stroke":186,"style":104},"118.4","576.3",[122,1629,1632],{"x":1630,"y":1622,"style":1631},"702.6","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","4.05",[88,1634],{"x":740,"y":1635,"width":1618,"height":177,"rx":365,"fill":102,"stroke":103,"style":132},"56.0",[122,1637,205],{"x":1621,"y":1638,"style":1623},"74.5",[88,1640],{"x":1626,"y":1635,"width":1641,"height":177,"rx":365,"fill":231,"stroke":232,"style":104},"526.5",[122,1643,1646],{"x":1644,"y":1638,"style":1645},"652.8","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","3.70",[88,1648],{"x":740,"y":1649,"width":1618,"height":177,"rx":365,"fill":102,"stroke":103,"style":132},"96.0",[122,1651,210],{"x":1621,"y":1652,"style":1623},"114.5",[88,1654],{"x":1626,"y":1649,"width":1655,"height":177,"rx":365,"fill":744,"stroke":745,"style":104},"483.8",[122,1657,1660],{"x":1658,"y":1652,"style":1659},"610.2","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","3.40",[122,1662,1666],{"x":1663,"y":1664,"style":1665},"380.0","146.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Price and features carry the most weight, which favours Vendor A.",[25,1668,1670],{"id":1669},"when-a-radar-chart-is-the-wrong-choice","When a radar chart is the wrong choice",[10,1672,1673],{},"Radar charts work for a handful of options on five to eight criteria that share one scale. With more options the shapes turn into a tangle; with criteria on different scales — cost in euros next to a satisfaction score — the shape is meaningless. For ranking, a sorted bar chart of the weighted score is clearer; for seeing each criterion, a grouped bar chart puts the scores side by side on a straight axis, which people judge more accurately than distances along spokes.",[25,1675,1677],{"id":1676},"performance-notes","Performance notes",[10,1679,1680],{},"Charts cost almost nothing to write: openpyxl stores a reference to the cells, not the data, so a radar chart over six criteria and three vendors adds a few kilobytes of XML regardless of how the numbers change. What does take time is producing many of them — one chart per candidate in a hiring review, say. In that case generate the charts in one workbook, one per sheet, in a single run, and render the whole workbook to PDF in one LibreOffice call for checking. Keep each chart's data on the same sheet as the chart, or on a clearly named data sheet, so that reviewers who open the file can see where every point comes from.",[25,1682,1684],{"id":1683},"common-pitfalls","Common pitfalls",[1686,1687,1688,1704,1717,1726,1732],"ul",{},[1689,1690,1691,1695,1696,1699,1700,1703],"li",{},[1692,1693,1694],"strong",{},"Automatic scaling."," Fix ",[37,1697,1698],{},"y_axis.scaling.min"," and ",[37,1701,1702],{},"max"," to the scoring range.",[1689,1705,1706,1709,1710,1699,1713,1716],{},[1692,1707,1708],{},"Hidden axes in Excel."," Set ",[37,1711,1712],{},"x_axis.delete = False",[37,1714,1715],{},"y_axis.delete = False","; LibreOffice shows axes regardless, so a LibreOffice preview can hide the problem.",[1689,1718,1719,1722,1723,1725],{},[1692,1720,1721],{},"Filled series covering each other."," Use ",[37,1724,644],{}," for more than one series.",[1689,1727,1728,1731],{},[1692,1729,1730],{},"Reading area as quality."," Area depends on spoke order; show a weighted score alongside.",[1689,1733,1734,1737],{},[1692,1735,1736],{},"Re-saving with openpyxl."," Loading a workbook drops its charts; add charts in the final save.",[25,1739,1741],{"id":1740},"conclusion","Conclusion",[10,1743,1744,1745,1748,1749,1751,1752,1754],{},"Build a ",[37,1746,1747],{},"RadarChart"," with one series per option and the criteria as categories, choose the ",[37,1750,644],{}," type for comparisons, fix the value axis to the scoring range with ",[37,1753,719],{}," on both axes, and give every series its own colour and marker shape. Render with LibreOffice to check it, and put a weighted score table next to the chart so the decision rests on the weights, not on the look of the shapes.",[25,1756,1758],{"id":1757},"frequently-asked-questions","Frequently asked questions",[10,1760,1761,1764],{},[1692,1762,1763],{},"How do I make a radar chart in Excel with Python?","\nUse openpyxl's RadarChart: add the score columns with add_data(..., titles_from_data=True), set the criteria as categories with set_categories, and place it with ws.add_chart. Set chart.type to \"marker\", \"standard\" or \"filled\".",[10,1766,1767,1770],{},[1692,1768,1769],{},"Why doesn't Excel show the axes on my openpyxl radar chart?","\nopenpyxl writes axes marked as deleted by default. Set chart.x_axis.delete = False and chart.y_axis.delete = False. LibreOffice displays the axes either way, so check in Excel.",[10,1772,1773,1776],{},[1692,1774,1775],{},"How do I set the radar chart scale from 0 to 5?","\nSet chart.y_axis.scaling.min = 0, chart.y_axis.scaling.max = 5 and chart.y_axis.majorUnit = 1 so the rings mark each score.",[10,1778,1779,1782],{},[1692,1780,1781],{},"What is the difference between standard, marker and filled radar charts?","\nStandard draws lines only, marker adds a point at each value, and filled shades each series' area. Filled charts work for one or two series; with more, later series cover earlier ones.",[25,1784,1786],{"id":1785},"related","Related",[1686,1788,1789,1795,1802,1809],{},[1689,1790,1791,1792,1794],{},"Up one level: ",[19,1793,22],{"href":21}," — every chart type openpyxl supports.",[1689,1796,1797,1801],{},[19,1798,1800],{"href":1799},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fformat-chart-axes-with-openpyxl\u002F","Format Chart Axes with openpyxl"," — scaling, units and number formats on axes.",[1689,1803,1804,1808],{},[19,1805,1807],{"href":1806},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fstyle-chart-colors-and-data-labels-with-openpyxl\u002F","Style Chart Colours and Data Labels with openpyxl"," — series styling in depth.",[1689,1810,1811,1815],{},[19,1812,1814],{"href":1813},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-bar-chart-in-excel-with-openpyxl\u002F","Create Bar Chart in Excel with openpyxl"," — the clearer choice for rankings.",[1817,1818,1819],"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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":419,"depth":419,"links":1821},[1822,1823,1824,1825,1826,1827,1828,1829,1830,1831,1832,1833],{"id":27,"depth":419,"text":28},{"id":389,"depth":419,"text":390},{"id":655,"depth":419,"text":656},{"id":842,"depth":419,"text":843},{"id":1086,"depth":419,"text":1087},{"id":1178,"depth":419,"text":1179},{"id":1669,"depth":419,"text":1670},{"id":1676,"depth":419,"text":1677},{"id":1683,"depth":419,"text":1684},{"id":1740,"depth":419,"text":1741},{"id":1757,"depth":419,"text":1758},{"id":1785,"depth":419,"text":1786},"2026-09-29","Compare several options across the same criteria — vendors, products, candidates, regions — with an Excel radar (spider) chart built from Python: series from a scoring table, a fixed 0–5 scale, visible axes, marker and filled styles, one colour per option, and a weighted score table next to it so the chart is not the only basis for a decision.","md",[1838,1840,1842,1844],{"q":1763,"a":1839},"Use openpyxl's RadarChart: add the score columns with add_data(..., titles_from_data=True), set the criteria as categories with set_categories, and place it with ws.add_chart. Set chart.type to \"marker\", \"standard\" or \"filled\".",{"q":1769,"a":1841},"openpyxl writes axes marked as deleted by default. Set chart.x_axis.delete = False and chart.y_axis.delete = False. LibreOffice displays the axes either way, so check in Excel.",{"q":1775,"a":1843},"Set chart.y_axis.scaling.min = 0, chart.y_axis.scaling.max = 5 and chart.y_axis.majorUnit = 1 so the rings mark each score.",{"q":1781,"a":1845},"Standard draws lines only, marker adds a point at each value, and filled shades each series' area. Filled charts work for one or two series; with more, later series cover earlier ones.","create a radar chart with openpyxl",null,{"breadcrumb":1849},[1850,1853,1856],{"name":1851,"item":1852},"Home","\u002F",{"name":1854,"item":1855},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl",{"title":1859,"description":1860},"Excel Radar (Spider) Chart with openpyxl","Build a radar chart with openpyxl RadarChart: marker, standard and filled types, y_axis scaling fixed to the score range, axis.delete = False so Excel shows the axes, series colours and markers, plus a weighted score table and a LibreOffice render check.","create-a-radar-chart-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-radar-chart-with-openpyxl\u002Findex",[1864,1865],"charts-and-images","openpyxl","how-to","4gupicU8VWaj_VPcypFAr9QnPsqP7NmNEID4QPnRaww",[1869,1873],{"title":1870,"path":1871,"stem":1872,"children":-1},"Create a Pie Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-pie-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-pie-chart-in-excel-with-openpyxl\u002Findex",{"title":1874,"path":1875,"stem":1876,"children":-1},"Create a Scatter Chart in Excel with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-scatter-chart-in-excel-with-openpyxl\u002Findex",[1878,1880],{"path":1879,"title":22},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl",{"path":1881,"title":1854},"\u002Fformatting-and-charting-excel-reports-with-python",[],[1884,1887,1890,1893,1896,1899],{"path":1885,"title":1886,"kind":1866},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","Build a Dashboard Sheet with Charts from Multiple Tabs",{"path":1888,"title":1889,"kind":1866},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","Build a Rolling 12-Month Chart on an Excel Dashboard",{"path":1891,"title":1892,"kind":1866},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fkeep-charts-and-images-when-filling-an-excel-template","Keep Charts and Images When Filling an Excel Template",{"path":1894,"title":1895,"kind":1866},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter","Add a Chart to an Excel File with xlsxwriter",{"path":1897,"title":1898,"kind":1866},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-sparklines-to-an-excel-report-with-xlsxwriter","Add Sparklines to an Excel Report with xlsxwriter",{"path":1900,"title":1901,"kind":1866},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fbuild-a-waterfall-chart-in-excel-with-python","Build a Waterfall Chart in Excel with Python",1790972111066]