Module:Plotter

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='   ' 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..'{{legend|white|Other ('..tostring(math.floor((100-valuesum)*1000000)/1000000)..'%)}}   ' output=mw.ustring.gsub(output,"", imgmap) if nowiki then return frame.preprocess(frame," "..output.."  ") 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 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 .. '<span style="position:absolute;color:'.. data[y].color .. ';'..data[y].color .. ';text-align:right;top:' .. et-10 .. 'px;width:'..leftreserve..'px;left:0px;">'.. j .. ' '          else debuglog=debuglog.."right";output=output .. '<span style="position:absolute;color:'.. data[y].color .. ';'..data[y].color .. ';text-align:left;top:' .. et-10 .. 'px;width:'..rightreserve..'px;left:'..leftreserve+reducedwidth..'px;">'.. 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