This is an old revision of this page, as edited by Wnt (talk | contribs) at 14:28, 6 April 2013 (Fouled up last time, trying again). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Revision as of 14:28, 6 April 2013 by Wnt (talk | contribs) (Fouled up last time, trying again)(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff) Module documentation[view] [edit] [history] [purge]This module is rated as pre-alpha. It is unfinished, and may or may not be in active development. It should not be used from article namespace pages. Modules remain pre-alpha until the original editor (or someone who takes one over if it is abandoned for some time) is satisfied with the basic structure. |
Note: now that this module's pie chart capability has been added to Module:Chart, this module is starting to look obsolete by comparison. It is therefore rated pre-alpha and not for general use, probably pending a total takeover by Module:Chart. It's being kept around only in case there are some functions left the latter hasn't matched yet, which I haven't checked.
This module has three plotting capabilities so far. Some of the code is crude as I began back when I'd first heard of Lua. They are all written independently and not even the parameters are standardized between them, nor do they share any subroutines. "Main" needs major work, "bar" is a bit better but not as good as Module:Chart at present, and "piechart" is closely based on Template:Pie chart.
function "piechart"
This uses the miter joining of border elements in HTML/CSS to draw pie-shaped slices. I still don't fully understand the code taken from Template:Pie chart but have made it a bit more comprehensible. The main improvement so far is that it is resizable and not limited to 10 slices.
New parameters are
- Radius (default 100) - radius of the pie chart
- nowiki - include a nonblank value to read rather than graph the output
To quote the piechart template
- option "thumb" specifies which side of the page the chart is floated to and defaults to
right
, as with image files. To make the chart appear on the left side of the page, specifythumb=left
. - "caption" is a string of text that appears on a line just before the legend.
- option "
other
", if specified, will cause an "Other" item to appear in the legend. (don't know if I implemented that) - each "labelN" is a string of text that appears in the legend entry for a slice. Omitting it will cause a legend entry to not be shown for that slice.
- each "valueN" is the percentage that the slice represents. Do not include the percent sign. (Need to fix that) Also note that it is shown in the legend as written (just after the label), without any rounding or other reformatting.
- each "colorN" is a CSS color code or name. See Module:Plotter/DefaultColors for the default values. This can be overridden with:
- "colorset" - specifies a Module: space file to get a list of color names and values
Note that the nowiki example given at the beginning of Template:Pie chart's documentation doesn't work, but it doesn't work for that template either. If there was ever some way to specify colors with numbers like 2 or 8, I think it's been forgotten.
Lua error in package.lua at line 80: module 'Module:Plotter/DefaultColors' not found.
Lua error in package.lua at line 80: module 'Module:Plotter/DefaultColors' not found.
function "main"
This is a demonstrative scatter plot function that doesn't even have labels added yet.
- icon (an image to display at each point)
- iconradius (default 10 - a rough measurement of the icon's size to help position dashed lines)
- lineicon (cruddy text icon "•" - should be replaced by divs)
- lineiconradius (default 5)
- plotsizex (default 100)
- plotsizey (default 100)
- plotstep (default 10) distance between dashes
- unnamed arguments are x and y coordinates of multiple points
function "bar"
This produces a bar chart. It has labels but I'm still working on the axis...
local delimiter = args.delimiter or pargs.delimiter or ","
- width (default 200)
- height (default 200)
- normalize - a list of N numbers corresponding to data series 1 to N. Each number identifies which series a series should be normalized to. So "1 2 1 1 3" will plot series 1,4,5 relative to one another, but 2 and 5 will be on their own scale (with their own axis labels, eventually)
- delimiter (default comma) separates values in group1, group2, etc.
- group1, group2, group3 etc. Each is a list of numeric values separated by delimiter
- xlegend - labels for each position on the x axis separated by delimiter. It is assumed all series use the same x values.
- ylegend - labels for each series of data (group1, group2, etc.)
Editors can experiment in this module's sandbox (create | mirror) and testcases (create) pages.
Subpages of this module.
local p={} function pick(a,n) return a 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 local c=ct 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='<div class="transborder" style="position:absolute;width:'..radius..'px;line-height:0px;'..lr..':'..lrv..'px;top:'..tv..'px;border-width:'..bw1..'px '..bw2..'px '..bw3..'px '..bw4..'px;border-'..bd..'color:'..color..';"></div>' if quadrant==1 or quadrant==2 or quadrant==3 then output=output..'<div style="position:absolute;line-height:0px;border-style:solid;'..lrB..':0px;top:0px;border-width:0px '..bw2B..'px '..radius..'px 0px;border-color:'..color..';"></div>' if quadrant==3 then output=output.. '<div style="position:absolute;line-height:0px;border-style:solid;left:0px;top:0px;border-width:0px '..radius..'px '..2*radius..'px 0px;border-color:'..color..';"></div>' 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=j else k=tonumber(mw.ustring.match(i,"value(%d*)")) if k then value=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=j end end end end output='<div style="position:absolute;left:0;top:0">]</div> </div> <!-- Legend --> <div class="thumbcaption"> ' 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=j or "" else k=tonumber(mw.ustring.match(i,"value(%d*)")) if k then value=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=j or "" else k=tonumber(mw.ustring.match(i,"link(%d*)")) if k then link=j or "" end end end end end local valuesum=0 -- sum of all slices local imgmap="" -- beginning of a polygon specification for <imagemap> for slice=1,slicecount do if value then if link then -- center of the circle, NOTE coords are relative to 600 px image before scaling NOT the radius imgmap=imgmap.."poly 300 300" for x=valuesum,valuesum+value do local sin=math.floor(300*math.sin(x*math.pi/50)) local cos=math.floor(300*math.cos(x*math.pi/50)) imgmap=imgmap.." "..300+cos.." "..300-sin end imgmap=imgmap.." .."]]\n" end valuesum=valuesum+value output=piechartslice(color,valuesum,radius)..output.."{{legend|"..(color or "").."|"..(label or "").." ("..valuesum.."%)}}" end end imgmap='<div style="position:absolute;width:'..2*radius..'px;height:'..2*radius..'px;z-value:1000;>\n<imagemap>\nFile:Transparent600.gif|'..radius..'px\n'..imgmap..'desc none\n</imagemap></div>' if #link==0 then imgmap="" end -- make imgmap nothing blank if no links output='<div class="t'..thumb..'" style="margin-bottom:0.5em;width:auto;"><div style="border:1px solid #cccccc;padding:3px;background-color:#f9f9f9;font-size: 94%;text-align:center;width:'..2*radius..'px"> <!-- Graph --> <div style="background-color:white;margin:auto;position:relative;width:'..2*radius..'px;height:'..2*radius..'px;overflow:hidden;"> '..output.."{{legend|white|Other ("..tostring(math.floor((100-valuesum)*1000000)/1000000).."%)}}</div> </div>"..imgmap.."</div>" if nowiki then return frame.preprocess(frame,"<pre><nowiki>"..output.."</nowiki></pre>") 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 = ] .. plotsizex+(2*iconradius) .. ] .. plotsizey+(2*iconradius) .. ] if (args or pargs) ~= nil then local x=(args or pargs)+0 local y=(args or pargs)+0 local xmin = x local xmax = x local ymin = y local ymax = y local index = 3 while (args or pargs) ~= nil do local x=(args+0 or pargs+0) local y=(args+0 or pargs+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 ~= nil then local x=(args or pargs)+0 local y=(args or pargs)+0 local plotx=math.floor(plotsizex*(x-xmin)/(xmax-xmin)) local ploty=math.floor((plotsizey-plotsizey*(y-ymin)/(ymax-ymin))) output = output .. ] .. plotx .. ] .. ploty .. ] .. icon .. "</span>" lastx = plotx lasty = ploty end index = 3 while (args or pargs) ~= nil do local x=(args or pargs)+0 local y=(args or pargs)+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 .. ] .. lastx+linefix+math.floor(delx*(plotparm/plotdist)) .. ] .. lasty+linefix+math.floor(dely*(plotparm/plotdist)) .. ] .. lineicon .. "</span>" plotparm = plotparm - plotstep end lastx = plotx lasty = ploty end output = output .. ] .. plotx .. ] .. ploty .. ] .. icon .. "</span>" index = index + 2 end else output = "error" end output = output .. "</div>" return output end -- data structure is -- data.value -- maxyval -- data.color -- data.legend -- data.legend 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 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={} --- pull in the "groupN" data (delimited) --> text local text=args -- 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.origin=args or 0 data.origin=tonumber(data.origin) data.max=data.origin;data.min=data.origin debuglog=debuglog.."I"..yseries..tostring(norm) --- set ngroup to whatever its norm points at, or new if norm then if ngroup] then ngroup=ngroup] else nngroup=nngroup+1 ngroup=nngroup end else ngroup=1 -- if no norm specified, just dump to the first series group end ---- pull in the actual values prowl=mw.ustring.gmatch(text,"(+)") x=0 repeat x=x+1 data={} data.value=prowl() debuglog=debuglog.."V"..x..yseries..tostring(data.value) if not(data.value) then if x>maxx then maxx = x-1 end; break end data.value=tonumber(data.value) if data.max then if data.value>data.max then data.max=data.value end else data.max=data.value end if data.min then if data.value<data.min then data.min=data.value end else data.min=data.value end until false ---- pull in the colorN="whatever" data.color=args or "" -- one color for yseries group; can be nil if data.color=="" then data.color="black" end until false --- import the xlegends for each group prowl=mw.ustring.gmatch(args.xlegend,"+") x=0 data.legend={} -- for x legends, y="legend" repeat x=x+1 data.legend=prowl() if not (data.legend) then break end data.legend=data.legend until false --- import the ylegends for each group prowl=mw.ustring.gmatch(args.ylegend,"+") yseries=0 repeat yseries=yseries+1 data.legend=prowl() until not (data.legend) -- set the maxval] = data[(any series in ngroup).max yseries=0 repeat yseries=yseries+1 if not(data.max) then break end debuglog=debuglog..tostring(yseries)..":"..tostring(ngroup) .. ">"..tostring(data.max) if maxyval] then if data.max>maxyval] then maxyval]=data.max end else maxyval]=data.max;debuglog=debuglog.."A"..tostring(data.max)..tostring(data.min) end if minyval] then if data.min<minyval] then minyval]=data.min end else minyval]=data.min;debuglog=debuglog.."A"..tostring(data.min) end until false --- Draw the output local output = ] .. width .. ] .. height .. ] local output='<div style="position:relative;overflow:visible;border-style:solid;border-color: #0077ff;width:' .. width .. 'px;height:' .. height .. 'px;">' 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)..tostring(data) .. tostring(maxyval])..tostring(minyval]) if data and maxyval] then local pw=(data.value-data.origin)/(maxyval]-minyval]) -- proportion of value to the max value for that y-series local po=(data.origin-minyval])/(maxyval]-minyval]) 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..'<div style="position:absolute;background-color:' .. data.color .. ';width:' .. ew .. 'px;height:' .. eh .. 'px;top:' .. et .. 'px;left:' .. el .. 'px;"></div>' end -- if data and maxval] end -- for x = 1, maxx end -- for y=1,maxy ---- draw the ylegends for x = 1,maxx do output=output .. '<span style="position:absolute;top:'.. reducedheight+topreserve .. 'px;left:'..leftreserve+math.floor(( (x-1)*(maxy+1)+(maxx/2) )*ew)..'px;">'..data.legend..'</span>' end for y = 1,maxy do output=output .. '<span style="position:absolute;color:'.. data.color .. ';top:' .. (y-1)*20 .. 'px;left:'.. (leftreserve+10) ..'px;">'.. (data.legend or "") ..'</span>' local point={minyval],data.origin,maxyval]} for i,j in ipairs(point) do local po=(j-minyval])/(maxyval]-minyval]) local et=topreserve+math.floor((1-po)*reducedheight) debuglog=debuglog.."pass" .. y .. ngroup if tonumber(ngroup)==1 then debuglog=debuglog.."left";output=output .. '<span style="position:absolute;color:'.. data.color .. ';'..data.color .. ';text-align:right;top:' .. et-10 .. 'px;width:'..leftreserve..'px;left:0px;">'.. j .. '</span>' else debuglog=debuglog.."right";output=output .. '<span style="position:absolute;color:'.. data.color .. ';'..data.color .. ';text-align:left;top:' .. et-10 .. 'px;width:'..rightreserve..'px;left:'..leftreserve+reducedwidth..'px;">'.. j .. '</span>' end end end debuglog=debuglog..tostring(maxyval)..tostring(maxyval)..tostring(maxyval)..tostring(data.max)..tostring(data.max)..tostring(data.max)..tostring(minyval)..tostring(minyval)..tostring(minyval)..tostring(data.min)..tostring(data.min)..tostring(data.min)..data.legend..data.legend..data.legend output = output .. "</div>\n" if (args.debug or pargs.debug) then output=output..debuglog end return output end return p