Module:Wnt/Templates/Svgedit
Documentation for this module may be created at Module:Wnt/Templates/Svgedit/doc
local p={}
function findfirst(histo, value) -- this stupid algorithm should actually even if unsorted...
local lessthans=1
for i = 1, #histo do
if histo[i]<value then lessthans=lessthans+1 end
end
return lessthans -- warning - returns array size + 1 if there are no values >= value in the array
end
function extractprofile(profile, levels)
-- takes a profile like [[User:Wnt/Templates/Svgedit/Sampleprofile1]] and extracts the colors to use, according to how many levels are requested
profile = string.match(profile,levels .. "==(.+)") or "no match"
profile = string.match(profile,"(.-)==") or profile
local x=0
local debug=""
local pattern={}
for w in string.gmatch(profile, "%x%x%x%x%x%x") do
x=x+1
pattern[x]=w
debug = debug .. w
end
return pattern -- an array of levels (or, that's the idea...)
end
function readdata(datafile)
-- reads in messy data and turns it into an array of keys (id's) and numeric values
local values={} -- array of id's and numerical data values to be returned
for line in string.gmatch(datafile,"([^,;\n]*)") do -- everything on first line excluding delimiters return-comma-semicolon
local id=string.match(line,"(%a%w*)") -- id is first thing on the line beginning in a letter
local strvalue, exponent=string.match(line,"(\-?\+?%d+)E?e?(%d-)") -- captures signed number, exponent in 1.7E4 type notation.
value = strvalue -- * 10 ^ (exponent or 0) -- this better not be prone to errors
if id then values[id] = value end
end
return values
end
function createorders(datafile,profile,override)
-- reads in your actual data file (the numbers for each id) and tries to create a nice scheme for it
-- these three all come from frame parameters
-- if override is set, anything >= to a specific number is assigned to the given level (or higher)
-- override (an array) should be accepted for all levels, none, or just some...
-- set last level in override to 99999999999 or something to make it empty - function has to exclude empty level...
local maxlevel = string.match(profile,"(%d+)%s*==") + 0
-- maxlevel is the most allowed by the profile itself (largest numerical heading)
local values = {}
values = readdata(datafile) -- array of id's and numerical data values
local count=0
local histo={} -- histogram of all the values indexed by integer
for k,v in ipairs(values) do
count=count+1
histo[count]=v -- note values should be coerced to numerical before they're passed here
end
table.sort(histo)
local maxvalue=histo[count]
local threshold={} -- threshold(1 to maxlevel) is the number a value must be equal to or greater to be assigned that color value or greater
local threspos={} -- POSITION of threshold, which can go to count + 1 if none are greater to or equal to it
local threscount=0 -- number of thresholds ACTUALLY set in the override array (not counting numbers too high for any data to meet)
local thresdone={} -- which thresholds (1 to threscount) have been set already (value 1 to maxlevel)
for k,v in ipairs(override) do
if (v <= maxvalue) then
threshold[k] = v
threspos[k] = findfirst(histo,v)
else maxlevel = math.min(k-1, maxlevel)
end
-- don't set thresholds that won't be used; set the hard maxlevel to one less than the key if the key is defined so nothing can satisfy it (e.g. 99999999)
threscount=threscount+1
thresdone[threscount]=k -- array (integer index) of _which_ levels already set here
end
table.sort(thresdone)
thresdone[0] = 0 -- place holder; actually negative infinity value for "threshold 0"
threspos[0] = 1 -- "threshold 0" starts with the first value in the histogram
for x = 1,threscount do
local first=thresdone[x-1]
local firstpos=threspos[threshold[first]]
local last=thresdone[x]
local lastpos=threspos[threshold[last]]
for fillinlevel=first+1,last-1 do
threshold[fillinlevel]=findfirst(histo,math.floor(firstpos + (lastpos-firstpos)*(fillinlevel-first)/(last-first)))
end
-- absolute PUNT code here; would prefer to get nice round-numbered levels, decide linear versus log, etc. but if I don't paper this over I'll never test this thing
-- figure out how to set the thresholds for each segment where they're not defined yet
end
-- set the last thresholds after all those defined (or if none exist)
-- handling of the last segment _could be_ (but isn't presently) special - maxlevel may be reset for advantage...
local first=thresdone[threscount]
local firstpos=threspos[threshold[first]] or 1
local last=maxlevel+1
local lastpos=count+1
for fillinlevel=first+1,last-1 do
threshold[fillinlevel]=findfirst(histo,math.floor(firstpos + (lastpos-firstpos)*(fillinlevel-first)/(last-first)))
end
local pattern = extractprofile(profile,maxlevel) -- now have pattern[x] = the actual fill for level x
local orders={}
for k,v in ipairs(values) do
local x=1
while v < threshold[x] do x=x+1 end
orders[k] = pattern[x]
end
return orders --array of id's and color values
end
function replacetags(svginput,orders)
-- this function gets an array "orders" which contains tags and hexadecimal values the tags should be set to IF they exist. svginput is searched for tags with "id" fields and as they appear the "fill" contents are replaced with the "orders" value for that id. Designed only for basic tagging within the id field, not when it's done at the front of the file.
local ids={}
local fills={}
local index=1
local repdebug="replacetags debug output:" .. svginput
svgnew=svginput
for w in string.gmatch(svginput, "\".-\"") do
repdebug=repdebug.."w="..w
local fill = string.match(w, "fill:(.-);") or ""
repdebug=repdebug..fill
local id = string.match(w, "id=\"(.-)\"") or ""
repdebug=repdebug..id
if (id ~= "") then
if (fill ~= "") then
if orders[id] then
wnew = string.gsub (w, fill, orders[id]) -- first replace just this fill in this element
svgnew = string.gsub (svgnew, w, wnew) -- then replace this exact element in the new svg
end -- if no orders for id, don't touch anything, so no else
else
local style = string.match(w, "(style=\")") or ""
if (style ~= "") then -- shoehorn new fill at beginning of "style="
wnew = string.gsub (w, style, style .. "fill:" .. orders[id] .. ";")
svgnew = string.gsub (svgnew, w, wnew)
else -- no style at all!
local indent=""
local insert=""
insert, indent = string.match(w,"((%s*)id=\")") -- this had better not be nil...!
wnew = string.gsub (w, insert, (indent or "") .. "style=\"fill:" .. orders [id] .. ";\"\n" .. (indent or ""))
end -- end case of no style
end -- end case of no fill
end -- no else for id="" - if we don't have a name for the curve we can't easily figure out what it is, though in theory there could also be features to replace things by color without knowing what they're called
end
return svgnew,repdebug
end
function p.display(frame)
output="" -- only a debug function for now
local args=frame.args
local parent=frame:getParent()
local pargs=parent.args
local svginput=args.svginput or pargs.svginput or "ERROR"
local ids={}
local fills={}
local index=1
local reverse={}
for w in string.gmatch(svginput, "<.->") do
local fill = string.match(w, "fill:(.-);") or ""
local id = string.match(w, "id=\"(.-)\"") or ""
if fill ~= "" then output = output .. id .. " = " .. fill .. "\n" end
if (fill ~= "" and id ~= "") then
ids[index]=id
fills[index]=fill
index=index+1
if (reverse[fill]) then
reverse[fill] = reverse[fill] .. ", " .. id
else reverse[fill] = ":" .. id
end
end
end
output = output .. "\n " -- break before reverse lookup table output
for k,v in pairs(reverse) do output = output .. "\n " .. k .. v end
return output
end
function p.testextract(frame)
local profile=frame.args.profile
local levels=frame.args.levels
local a={}
a=extractprofile(profile, levels)
return a[7]
end
function p.makesvg(frame)
local args=frame.args
local parent=frame:getParent()
local pargs=parent.args
local svginput=pargs.svginput or args.svginput or "ERROR"
local profile=pargs.profile or args.profile or "==1== 000000" -- just paints all black
local presets=pargs.presets or args.presets or ""
local datafile=pargs.datafile or args.datafile or ""
local z=1
local preset=""
local preposn=1
local override={}
local debug=""
local repdebug=""
debug = debug .. presets
for preset in string.gmatch(presets,"([%d\?]+)") do
if preset ~= "?" then override[z]=preset; debug=debug .. "preset" .. z .. preset end
z=z+1
end
svgoutput,repdebug=replacetags(svginput,createorders(datafile,profile,override))
return ((repdebug or "") .. debug .. "\n" .. "<pre>" .. svgoutput .. "</pre>") -- pre tags to keep output from being wikimunged!
end
return p