No edit summary |
No edit summary |
||
Line 17: | Line 17: | ||
}, | }, | ||
} | } | ||
options.function_space = { | |||
x = options.from.x, | |||
y = options.from.y, | |||
w = options.to.x - options.from.x, | |||
h = options.to.y - options.from.y, | |||
} | |||
options.content_space = {x = 0, y = 0, w = 1000, h = 1000 * options.height / options.width} | |||
options.svg_size = {w = options.content_space.w, h = options.content_space.h} | |||
if options.from.x > -20 then options.svg_size.w = options.svg_size.w + 20 end | |||
if options.from.y > -20 then options.svg_size.h = options.svg_size.h + 20 end | |||
local svg = mw.html.create() | local svg = mw.html.create() | ||
Line 25: | Line 37: | ||
:wikitext(frame:callParserFunction("#tag:svg", { | :wikitext(frame:callParserFunction("#tag:svg", { | ||
tostring(svg), | tostring(svg), | ||
viewBox = | viewBox = "0 0 1000 "..(1000 * options.height / options.width), | ||
style = "width: 100%; | style = "width: 100%; fill: none;", | ||
})) | })) | ||
return tostring(fig) | return tostring(fig) | ||
Line 45: | Line 57: | ||
local y = tonumber(str:sub(i + 1)) | local y = tonumber(str:sub(i + 1)) | ||
return { x=x, y=y } | return { x=x, y=y } | ||
end | |||
-- Drawing | |||
function create_transform(from, to) | |||
-- (x-from.x) * (to.w/from.w) + to.x | |||
-- <=> x * (to.w/from.w) - from.x * (to.w/from.w) + to.x | |||
-- |----sx-----| |-----------tx--------------| | |||
local sx, sy = to.w/from.w, to.h/from.h | |||
local tx = from.x * (to.w/from.w) + to.x | |||
local ty = from.y * (to.h/from.h) + to.y | |||
local function transform_x(x) return x * sx - tx end | |||
local function transform_y(y) return y * sy - ty end | |||
return transform_x, transform_y, sx, sy | |||
end | end | ||
Line 52: | Line 78: | ||
if step_x <= 0 then error("Invalid x step size") end | if step_x <= 0 then error("Invalid x step size") end | ||
if step_y <= 0 then error("Invalid y step size") end | if step_y <= 0 then error("Invalid y step size") end | ||
local min_x = options. | |||
local min_y = options. | local tx, ty, sx, sy = create_transform(options.function_space, options.content_space) | ||
local | step_x = step_x * sx | ||
local | step_y = step_y * sy | ||
local min_x = options.content_space.x | |||
local min_y = options.content_space.y | |||
local max_x = options.content_space.w + min_x | |||
local max_y = options.content_space.h + min_y | |||
local origin_x, origin_y = tx(options.origin.x), ty(options.origin.y) | |||
local path = "" | local path = "" | ||
local k | local k | ||
-- +x | -- +x | ||
k = | k = origin_x + step_x | ||
while(k < | while(k < max_x) do | ||
path = path.."M"..k.." "..min_y.."V".. | path = path.."M"..k.." "..min_y.."V"..max_y | ||
k = k + step_x | k = k + step_x | ||
end | end | ||
-- -x | -- -x | ||
k = | k = origin_x - step_x | ||
while(k > | while(k > min_x) do | ||
path = path.."M"..k.." "..min_y.."V".. | path = path.."M"..k.." "..min_y.."V"..max_y | ||
k = k - step_x | k = k - step_x | ||
end | end | ||
-- +y | -- +y | ||
k = | k = origin_y + step_y | ||
while(k < | while(k < max_y) do | ||
path = path.."M"..min_x.." "..k.."H".. | path = path.."M"..min_x.." "..k.."H"..max_x | ||
k = k + step_x | k = k + step_x | ||
end | end | ||
-- -y | -- -y | ||
k = | k = origin_y - step_y | ||
while(k > | while(k > min_y) do | ||
path = path.."M"..min_x.." "..k.."H".. | path = path.."M"..min_x.." "..k.."H"..max_x | ||
k = k - step_x | k = k - step_x | ||
end | end | ||
svg:tag("path") | svg:tag("path") | ||
:attr("stroke", "#666") | :attr("stroke", "#666") | ||
:attr("stroke-width", "1") | |||
:attr("stroke-linecat", "butt") | |||
:attr("d", path) | :attr("d", path) | ||
:done() | :done() | ||
end | end | ||
function new_object(self, o) | |||
function | |||
o = o or {} | o = o or {} | ||
setmetatable(o, self) | setmetatable(o, self) | ||
Line 102: | Line 135: | ||
end | end | ||
function | -- Expression Logic | ||
local ExprCtx = {new=new_object} | |||
function ExprCtx:lookup(name) | |||
return self.vars and self.bars[name] or math[name] or error("Unknown variable: "..name) | return self.vars and self.bars[name] or math[name] or error("Unknown variable: "..name) | ||
end | end | ||
Line 115: | Line 151: | ||
function eval_expr(str, vars) | function eval_expr(str, vars) | ||
local cx = | local cx = ExprCtx:new{vars=vars} | ||
return parse_expr(str):eval(cx) | return parse_expr(str):eval(cx) | ||
end | end | ||
return p | return p |
Revision as of 20:20, 11 February 2024
Lua error at line 6: Missing 'Functions'.
Arguments
Argument | Description | Example | Default |
---|---|---|---|
Plots | Plots as JSON | See #Example | |
From | Lowest x,y to plot | -pi/2,0 |
0,0
|
To | Highest x,y to plot | 10^2,2 |
|
Origin | X/Y Axis origin | 0.5,0.5 |
0,0
|
GridStep | Coordinate grid step size | pi/2,1 |
1,1
|
Inline | Display the plot inline instead of floating | true |
(absent) |
Axis label (not implemented) | |||
LabelStepX | Label for grid X-axis increments | format("%sπ", dfrac(x/pi, 1)) |
format("%d", x)
|
LabelStepY | Label for grid X-axis increments | format("%.1f", y) |
format("%d", x)
|
ColorX | X-axis color | #44f |
#f44
|
ColorY | Y-axis color | #0f4 |
#0f0
|
Underlined Arguments are required.
Available Functions and Constants in Expressions
printf(format, ...args)
See Lua string.format.dfrac(number, is_factor)
Formats a float as a fraction, if possible. If it is a factor, then the number 1 or -1 will only produce its sign as an output, useful when formatting as a factor of pi, for example. See #Example below.sin,floor,abs,...
Any constant or function from the Lua math library can be used directly (without themath.
prefix).
Example
{{#invoke:ColinTheCat/Plotter|Plot2D |From=-0.2, -1.2 |To=2 * pi + 0.2, 1.2 |GridStep=0.5 * pi, 1 |LabelStepX=format("%sπ", dfrac(x/pi, 1)) |Plots= [ {"Type": "function", "Function": "sin(x)", "Samples": 100, "Label": "sin"}, {"Type": "function", "Function": "cos(x)", "Samples": 100, "Label": "cos"}, {"Type": "function", "Function": "tan(x)", "Samples": 35, "Label": "tan", "Ranges": [ {"To": "(0.5-0.2)*pi"}, {"From": "(0.5+0.2)*pi", "To": "(1.5-0.2)*pi"}, {"From": "(1.5+0.2)*pi"} ]} ] }}
Lua error at line 6: Missing 'Functions'.
local p = {}
local Expression = require("Module:ColinTheCat/Expression")
function p.Plot2D(frame)
assert(frame.args.Functions, "Missing 'Functions'")
assert(frame.args.To, "Missing 'To'")
local options = {
functions = mw.text.jsonDecode(frame.args.Functions),
from = parse_xy(frame.args.From or "0 0"),
to = parse_xy(frame.args.To),
origin = parse_xy(frame.args.Origin or "0 0"),
grid_step = {
x=frame.args.GridStepX or "1",
y=frame.args.GridStepY or "1",
},
}
options.function_space = {
x = options.from.x,
y = options.from.y,
w = options.to.x - options.from.x,
h = options.to.y - options.from.y,
}
options.content_space = {x = 0, y = 0, w = 1000, h = 1000 * options.height / options.width}
options.svg_size = {w = options.content_space.w, h = options.content_space.h}
if options.from.x > -20 then options.svg_size.w = options.svg_size.w + 20 end
if options.from.y > -20 then options.svg_size.h = options.svg_size.h + 20 end
local svg = mw.html.create()
draw_grid(svg, options)
local fig = mw.html.create("div")
:wikitext(frame:callParserFunction("#tag:svg", {
tostring(svg),
viewBox = "0 0 1000 "..(1000 * options.height / options.width),
style = "width: 100%; fill: none;",
}))
return tostring(fig)
end
function parse_aspect(str)
local i = str:find(":", 1, false)
if i == nil then return tonumber(str) end
local y = tonumber(str:sub(1, i - 1))
local x = tonumber(str:sub(i + 1))
return x/y
end
function parse_xy(str)
local i = str:find(" ", 1, false)
if i == nil then error("Invalid xy: "..str) end
local x = tonumber(str:sub(1, i - 1))
local y = tonumber(str:sub(i + 1))
return { x=x, y=y }
end
-- Drawing
function create_transform(from, to)
-- (x-from.x) * (to.w/from.w) + to.x
-- <=> x * (to.w/from.w) - from.x * (to.w/from.w) + to.x
-- |----sx-----| |-----------tx--------------|
local sx, sy = to.w/from.w, to.h/from.h
local tx = from.x * (to.w/from.w) + to.x
local ty = from.y * (to.h/from.h) + to.y
local function transform_x(x) return x * sx - tx end
local function transform_y(y) return y * sy - ty end
return transform_x, transform_y, sx, sy
end
function draw_grid(svg, options)
local step_x = eval_expr(options.grid_step.x)
local step_y = eval_expr(options.grid_step.y)
if step_x <= 0 then error("Invalid x step size") end
if step_y <= 0 then error("Invalid y step size") end
local tx, ty, sx, sy = create_transform(options.function_space, options.content_space)
step_x = step_x * sx
step_y = step_y * sy
local min_x = options.content_space.x
local min_y = options.content_space.y
local max_x = options.content_space.w + min_x
local max_y = options.content_space.h + min_y
local origin_x, origin_y = tx(options.origin.x), ty(options.origin.y)
local path = ""
local k
-- +x
k = origin_x + step_x
while(k < max_x) do
path = path.."M"..k.." "..min_y.."V"..max_y
k = k + step_x
end
-- -x
k = origin_x - step_x
while(k > min_x) do
path = path.."M"..k.." "..min_y.."V"..max_y
k = k - step_x
end
-- +y
k = origin_y + step_y
while(k < max_y) do
path = path.."M"..min_x.." "..k.."H"..max_x
k = k + step_x
end
-- -y
k = origin_y - step_y
while(k > min_y) do
path = path.."M"..min_x.." "..k.."H"..max_x
k = k - step_x
end
svg:tag("path")
:attr("stroke", "#666")
:attr("stroke-width", "1")
:attr("stroke-linecat", "butt")
:attr("d", path)
:done()
end
function new_object(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
-- Expression Logic
local ExprCtx = {new=new_object}
function ExprCtx:lookup(name)
return self.vars and self.bars[name] or math[name] or error("Unknown variable: "..name)
end
function parse_expr(str)
local tok = Expression.Tokenizer:new{str=str}
local expr = Expression.parse_expr(tok)
if not expr then error("Invalid expression: "..expr) end
if tok:peek() then error("Leftover tokens: "..tok.str) end
return expr
end
function eval_expr(str, vars)
local cx = ExprCtx:new{vars=vars}
return parse_expr(str):eval(cx)
end
return p