local p={}  function pick(a,n)     return a[n+1] end  function loadColorSet(page)     if not(page) then page="" end     if mw.ustring.sub(page,1,7) ~= "Module:" then page="Module:Plotter/DefaultColors" end     local ct=mw.loadData(page)     if not ct then ct=mw.loadData("Module:Plotter/DefaultColors") end     local x=0     local color={}     local name={}     repeat         x=x+1         local n=ct[x*2-1]         local c=ct[x*2]         if not (n and c) then break end         table.insert(color,c)         table.insert(name,n)     until false     return color, name end      function piechartslice(color,percent,radius,link)     radius=radius or 100     local quadrant=math.floor(percent/25)     local sin=math.floor(radius*math.sin(percent*math.pi/50))     local cos=math.floor(radius*math.cos(percent*math.pi/50))     local tan25=math.floor(-1*radius*math.cos(percent*math.pi/50)/math.sin(percent*math.pi/50))     local output,lr,lrv,tv,bw1,bw2,bw3,bw4,bd,lrB,bw2B     local a={} -- throwaway array to make value matrix more apparent      -- quadrant 1 is upper left, quadrant 2 is lower left     lr=pick({'left','right','right','left','left'},quadrant)     lrv=pick({radius,radius,radius,radius,0},quadrant)     tv=pick({radius-sin,0,radius,radius,0},quadrant)      -- border width:bw1 (top) bw2 (right) bw3 (bottom) bw4 (left)     bw1=pick({0,0,-1*sin,radius,0},quadrant)     bw2=pick({0,tan25,-1*cos,0,2*radius},quadrant)     bw3=pick({sin,radius,0,0,2*radius},quadrant)     bw4=pick({cos,0,0,tan25,0},quadrant)     bd=pick({'bottom-','right-','top-','left-',''},quadrant)     lrB=pick({'n/a','right','left','left','n/a'},quadrant)      -- right border for second div (the bottom border is radius and others are zero)     bw2B=pick({'n/a',radius,2*radius,2*radius,'n/a'},quadrant)      local output=''     if quadrant==1 or quadrant==2 or quadrant==3 then         output=output..''         if quadrant==3 then             output=output.. ''         end     end     return output end      function p.piechart(frame)     local parent=frame.getParent(frame) or {}     local color=loadColorSet(frame.args.colorset or parent.args.colorset) or {'red','green','blue','yellow','fuchsia','aqua','brown','orange','purple','sienna'}     local value={}     local label={}     local link={}     local slicecount=0     local thumb,nowiki,radius     if parent.args then         thumb=parent.args.thumb         nowik=parent.args.nowiki         radius=parent.args.radius     end     thumb=frame.args.thumb or thumb     nowiki=frame.args.nowiki or nowiki     radius=frame.args.radius or radius or 100     radius=tonumber(radius)     if radius<1 then radius=100 end     if not(thumb) then thumb="right" end     if not(mw.ustring.match(thumb,"%S")) then thumb="right" end     for i,j in pairs(parent.args or {}) do -- I should look up if there's a way to union parent.args AND frame.args         local k=tonumber(mw.ustring.match(i,"color(%d*)"))         if k then color[k]=j         else k=tonumber(mw.ustring.match(i,"value(%d*)"))             if k then                 value[k]=tonumber(j)                 if k>slicecount then slicecount=k end -- not using #value to avoid randomness if some values are left out             else k=tonumber(mw.ustring.match(i,"label(%d*)"))                 if k then label[k]=j                 end             end         end     end      --- innermost absolute div around circle, then a second thumbcaption div around legend.  Note (/div)(div) at core between circle and legend.  The rest are accreted around this center.     output=''..(radius*2)..'px|link=<Module:Plotter internal imgmap insertion token> </div>   '     for i,j in pairs(frame.args or {}) do -- supersede parent.args values         local k=tonumber(mw.ustring.match(i,"color(%d*)"))         if k then color[k]=j or ""         else k=tonumber(mw.ustring.match(i,"value(%d*)"))             if k then                 value[k]=tonumber(j)                 if k>slicecount then slicecount=k end -- not using #value to avoid randomness if some values are left out             else k=tonumber(mw.ustring.match(i,"label(%d*)"))                 if k then label[k]=j or ""                 else k=tonumber(mw.ustring.match(i,"link(%d*)"))                     if k then link[k]=j or ""                     end                 end             end         end     end     local valuesum=0 -- sum of all slices     local imgmap="" -- beginning of a polygon specification for '     if #link==0 then imgmap="" end -- make sure imgmap is blank if no links      --- outer thumb tleft/tright is float/clear left or right      --- thumbinner encapsulates the graph      --- third relative div container ends in the middle of ..output..      --- next third div style "thumbcaption" begins in ..output..      --- all three end at end     output='   '..output..''     output=mw.ustring.gsub(output,"<Module:Plotter internal imgmap insertion token>", imgmap)     if nowiki then return frame.preprocess(frame,"<nowiki>"..output.."</nowiki>") else return frame.preprocess(frame,output) end end      function p.main(frame)     local args=frame.args     local parent=frame.getParent(frame)     local pargs=parent.args or {}     local icon=args.icon or pargs.icon     local iconradius=args.iconradius or pargs.iconradius or 10     local lineicon=args.lineicon or pargs.lineicon or "•"     local lineiconradius=args.lineiconradius or pargs.lineiconradius or 5     local linefix=iconradius-lineiconradius     local plotsizex = args.plotsizex or pargs.plotsizex or 100     local plotsizey = args.plotsizey or pargs.plotsizey or 100     local plotstep = args.plotstep or pargs.plotstep or 10     local output = [[]]     if (args[2] or pargs[2]) ~= nil then         local x=(args[1] or pargs[1])+0         local y=(args[2] or pargs[2])+0         local xmin = x         local xmax = x         local ymin = y         local ymax = y         local index = 3         while (args[index+1] or pargs[index+1]) ~= nil do            local x=(args[index]+0 or pargs[index]+0)            local y=(args[index+1]+0 or pargs[index+1]+0)            if (x < xmin) then xmin = x end            if (x > xmax) then xmax = x end            if (y < ymin) then ymin = y end            if (y > ymax) then ymax = y end            index = index + 2         end         local lastx=0         local lasty=0         if args[2] ~= nil then             local x=(args[1] or pargs[1])+0             local y=(args[2] or pargs[2])+0             local plotx=math.floor(plotsizex*(x-xmin)/(xmax-xmin))             local ploty=math.floor((plotsizey-plotsizey*(y-ymin)/(ymax-ymin)))             output = output .. [[]] .. icon .. ""             lastx = plotx             lasty = ploty         end         index = 3         while (args[index+1] or pargs[index+1]) ~= nil do             local x=(args[index] or pargs[index])+0             local y=(args[index+1] or pargs[index+1])+0             local plotx=math.floor(plotsizex*(x-xmin)/(xmax-xmin))             local ploty=math.floor((plotsizey-plotsizey*(y-ymin)/(ymax-ymin)))             if plotstep+0 ~= 0 then                local delx=plotx-lastx                local dely=ploty-lasty                plotdist=math.sqrt(delx*delx+dely*dely)                plotparm=plotdist-iconradius-plotstep/2                while plotparm>iconradius+lineiconradius+plotstep/2 do                   output = output .. [[]] .. lineicon .. ""                   plotparm = plotparm - plotstep                end                lastx = plotx                lasty = ploty             end             output = output .. [[]] .. icon .. ""             index = index + 2         end     else output = "error"     end     output = output .. ""     return output end  -- data structure is -- data[y][x].value -- maxyval[y] -- data[y].color -- data[y].legend -- data.legend[x]  function p.bar(frame)     local debuglog=""     local args=frame.args     local parent=frame.getParent(frame)     local pargs=parent.args or {}     local delimiter = args.delimiter or pargs.delimiter or ","     local width = args.width or pargs.width or 200     local height = args.height or pargs.height or 200           ---- Set up the table of "norms".  Series 1 to N normalize to (%d+)     local normalize = args.normalize or pargs.normalize or ""     local prowl=mw.ustring.gmatch(normalize,"(%d+)")     norm={}     local ngroup={} -- ngroup[yseries] identifies an index for ymax     local nngroup=0 -- the current maximum ngroup assigned     repeat        local t=prowl()        if not(t) then break end        t=tonumber(t)        table.insert(norm,t)     until false           --- import the actual data in group1 .. groupN     local yseries=0;local x=0     local data={} -- main data storage array     local maxy=0;local maxx=0; local maxyval={}; local minyval={} -- keeping these out of the data array after being driven half mad giving them cutesy names in the array!     repeat        yseries=yseries+1        data[yseries]={}         --- pull in the "groupN" data (delimited) --> text        local text=args["group"..yseries] -- each _group_ is a group of x-values in a y-series        if not (text) then maxy=yseries-1 break end                ---- pull in the originN=some number        data[yseries].origin=args["origin"..yseries] or 0        data[yseries].origin=tonumber(data[yseries].origin)        data[yseries].max=data[yseries].origin;data[yseries].min=data[yseries].origin        debuglog=debuglog.."I"..yseries..tostring(norm[yseries])         --- set ngroup[yseries] to whatever its norm points at, or new        if norm[yseries]        then if ngroup[norm[yseries]]            then ngroup[yseries]=ngroup[norm[yseries]]            else nngroup=nngroup+1                ngroup[yseries]=nngroup            end        else ngroup[yseries]=1 -- if no norm specified, just dump to the first series group        end         ---- pull in the actual values        prowl=mw.ustring.gmatch(text,"([^" .. delimiter .. "]+)")        x=0        repeat           x=x+1           data[yseries][x]={}           data[yseries][x].value=prowl()           debuglog=debuglog.."V"..x..yseries..tostring(data[yseries][x].value)           if not(data[yseries][x].value) then if x>maxx then maxx = x-1 end; break end           data[yseries][x].value=tonumber(data[yseries][x].value)           if data[yseries].max then if data[yseries][x].value>data[yseries].max then data[yseries].max=data[yseries][x].value end else data[yseries].max=data[yseries][x].value end           if data[yseries].min then if data[yseries][x].value<data[yseries].min then data[yseries].min=data[yseries][x].value end else data[yseries].min=data[yseries][x].value end        until false         ---- pull in the colorN="whatever"        data[yseries].color=args["color"..yseries] or "" -- one color for yseries group; can be nil        if data[yseries].color=="" then data[yseries].color="black" end     until false           --- import the xlegends for each group     prowl=mw.ustring.gmatch(args.xlegend,"[^" .. delimiter .. "]+")     x=0     data.legend={} -- for x legends, y="legend"     repeat        x=x+1        data.legend[x]=prowl()        if not (data.legend[x]) then break end        data.legend[x]=data.legend[x]     until false           --- import the ylegends for each group     prowl=mw.ustring.gmatch(args.ylegend,"[^" .. delimiter .. "]+")     yseries=0     repeat        yseries=yseries+1        data[yseries].legend=prowl()     until not (data[yseries].legend)           -- set the maxval[ngroup[(each series)]] = data[(any series in ngroup).max     yseries=0     repeat        yseries=yseries+1        if not(data[yseries].max) then break end        debuglog=debuglog..tostring(yseries)..":"..tostring(ngroup[yseries]) .. ">"..tostring(data[yseries].max)        if maxyval[ngroup[yseries]]        then if data[yseries].max>maxyval[ngroup[yseries]]            then maxyval[ngroup[yseries]]=data[yseries].max            end        else maxyval[ngroup[yseries]]=data[yseries].max;debuglog=debuglog.."A"..tostring(data[yseries].max)..tostring(data[yseries].min)        end        if minyval[ngroup[yseries]]        then if data[yseries].min<minyval[ngroup[yseries]]            then minyval[ngroup[yseries]]=data[yseries].min            end        else minyval[ngroup[yseries]]=data[yseries].min;debuglog=debuglog.."A"..tostring(data[yseries].min)        end      until false       --- Draw the output     local output = [[<div style="position:relative;border-style:solid;border-color: #0077ff;width:]] .. width .. px;height: .. height .. [[px;">]]     local output=''     local topreserve=20*(maxy)     local bottomreserve=20     local leftreserve=20     local rightreserve=20     local reducedheight=height-topreserve-bottomreserve     local reducedwidth=width-leftreserve-rightreserve     local ew=math.floor(reducedwidth/((maxx)*(maxy+1)))     for y = 1,maxy do         for x = 1, maxx do             debuglog=debuglog..y..x..tostring(ngroup[y])..tostring(data[y][x]) .. tostring(maxyval[ngroup[y]])..tostring(minyval[ngroup[y]])             if data[y][x] and maxyval[ngroup[y]]             then local pw=(data[y][x].value-data[y].origin)/(maxyval[ngroup[y]]-minyval[ngroup[y]]) -- proportion of value to the max value for that y-series                local po=(data[y].origin-minyval[ngroup[y]])/(maxyval[ngroup[y]]-minyval[ngroup[y]])                local eh=math.floor(pw*reducedheight)                local et=topreserve+math.floor(reducedheight - eh - po*reducedheight)                if eh<0 then eh=-1*eh;et=et-eh end -- pw can be negative; plot "backwards" looks the same                local el=leftreserve+math.floor(((x-1)*(maxy+1) + (y-1) + 0.5)*ew)                output=output..''             end -- if data[y][x] and maxval[ngroup[y]]         end -- for x = 1, maxx     end -- for y=1,maxy      ---- draw the ylegends     for x = 1,maxx do         output=output .. ''..data.legend[x]..''     end     for y = 1,maxy do         output=output .. ''.. (data[y].legend or "") ..''         local point={minyval[ngroup[y]],data[y].origin,maxyval[ngroup[y]]}         for i,j in ipairs(point) do            local po=(j-minyval[ngroup[y]])/(maxyval[ngroup[y]]-minyval[ngroup[y]])            local et=topreserve+math.floor((1-po)*reducedheight)            debuglog=debuglog.."pass" .. y .. ngroup[y]            if tonumber(ngroup[y])==1            then debuglog=debuglog.."left";output=output .. ''.. j .. ''            else debuglog=debuglog.."right";output=output .. ''.. j .. ''            end        end     end     debuglog=debuglog..tostring(maxyval[1])..tostring(maxyval[2])..tostring(maxyval[3])..tostring(data[1].max)..tostring(data[2].max)..tostring(data[3].max)..tostring(minyval[1])..tostring(minyval[2])..tostring(minyval[3])..tostring(data[1].min)..tostring(data[2].min)..tostring(data[3].min)..data.legend[1]..data.legend[2]..data.legend[3]     output = output .. "\n"     if (args.debug or pargs.debug) then output=output..debuglog end     return output end  return p