模組:FunctionGraph

local p = {}
local comp_number = nil
local getArgs = require('Module:Arguments').getArgs
local lib_calc = require('Module:Complex_Number/Calculate')
local TrackingCategory = require('Module:TrackingCategory')
local noop_func = function()end

function p.applyFunctionGraph(frame)
	local working_frame = mw.getCurrentFrame()
	local body = working_frame:extensionTag{ name = 'graph', content = require('Module:FunctionGraph/Graph').chart(frame) }
	return body
end
function p.functionGraph(frame)
	if comp_number == nil then comp_number = require("Module:Complex Number") end
    local cmath, qmath = comp_number.cmath.init(), comp_number.qmath.init()
	if not getArgs then getArgs = require('Module:Arguments').getArgs end
	local args = getArgs(frame, {parentFirst=true})
	local exprs = {}
	local body_args, x_start, x_end, y_min, y_max, sampling = {}, 0, 1, nil, nil, 50
	for arg_name, arg_value in pairs( args ) do
		local check_arg_name = mw.ustring.lower(tostring(arg_name))
		if tonumber(arg_name) ~= nil then exprs[#exprs + 1] = lib_calc._remove_strip_marker(arg_value)
		elseif check_arg_name == "start" then x_start = tonumber(arg_value)
		elseif check_arg_name == "end" then x_end = tonumber(arg_value)
		elseif check_arg_name == "sampling" then sampling = tonumber(arg_value)
		elseif check_arg_name == "min" then y_min = tonumber(arg_value)
		elseif check_arg_name == "max" then y_max = tonumber(arg_value)
		else body_args[arg_name] = arg_value
		end
	end
	local yesno = require('Module:Yesno')
	if yesno(args.useOtherModule or 'no') == true then lib_calc.use_other_module = true end
	
	local math_class = frame.args['class']or''
    local mymath = cmath
    local mytomath = cmath.toComplexNumber
    if mw.ustring.sub(math_class,1,7):upper()=="MODULE:" then
		local module_name, math_lib_name = lib_calc.checkModuleClass(math_class)
		xpcall(function()
			local load_module = require("Module:"..module_name)
			if load_module ~= nil then
				local load_math_lib = load_module[math_lib_name]
				if load_module ~= nil then
					local func_type = type(noop_func)
					local my_math_lib = (type(load_math_lib.init) == func_type) and load_math_lib.init() or load_math_lib
					if type(my_math_lib.constructor) == func_type then
						math_class = "mymath"
						mymath = my_math_lib
						mytomath = my_math_lib.constructor
					end
				end
			end
		end,noop_func)
	end

    local body = p._functionGraph(exprs,
    	x_start, x_end, sampling, y_min, y_max, body_args, ( {
		cmath = cmath,
		qmath = qmath,
		mymath = mymath,
	} ) [math_class] , (( {
		cmath = cmath.toComplexNumber,
		qmath = qmath.toQuaternionNumber,
		mymath = mytomath,
	} ) [math_class] ) )
	body.width = 400
	body.height = 100; body.type="line"
	body.interpolate = frame.args['interpolate'] or "monotone"
	for arg_name, arg_value in pairs( body_args ) do
		body[arg_name] = arg_value
	end
	--body = mw.getCurrentFrame():expandTemplate{title = "Graph:Chart", args = body}
	body = p.applyFunctionGraph(body)
	if use_ext_mathlib == true then TrackingCategory.append('使用擴充複變函數庫的頁面') end
	return body
end

function p.calc_table(frame)
	local variable_process = require("Module:Number/data")
    local args
    local can_math = false
    local should_math = false
    local show_math = false
    if frame == mw.getCurrentFrame() then
        -- We're being called via #invoke. The args are passed through to the module
        -- from the template page, so use the args that were passed into the template.
        args = getArgs(frame, {
        	parentFirst=true,
        	trim = false,
			removeBlanks = false
        }) --frame.args
        local yesno = require('Module:Yesno')
        can_math = yesno(args['use math'] or args['use_math'])
        should_math = yesno(args['should math'] or args['should_math'])
    else
        -- We're being called from another module or from the debug console, so assume
        -- the args are passed in directly.
        args = frame
    end
    lib_calc._randomseed()
    local yesno = require('Module:Yesno')
    if yesno(args.useOtherModule or 'no') == true then lib_calc.use_other_module = true end
    local first_number_list = {"2","3","4","5","6","7","8","9"}
    local second_number_list, first_number_show, second_number_show
    local calculate_str, calculate_title = "{{{left}}} * {{{right}}}", "×"
	if args['number list'] or args['number_list'] then first_number_list = mw.text.split(args['number list'] or args['number_list'] or '',',') end
	if args['number list2'] or args['number_list2'] then second_number_list = mw.text.split(args['number list2'] or args['number_list2'] or '',',') end
	
	if args['number list show'] or args['number_list_show'] then 
		first_number_show = mw.text.split(args['number list show'] or args['number_list_show'] or '',',') 
		if #first_number_show == 1 and mw.text.trim(first_number_show[1]) == '' then first_number_show = {} end 
	end
	if args['number list show2'] or args['number_list_show2'] then 
		second_number_show = mw.text.split(args['number list show2'] or args['number_list_show2'] or '',',') 
		if #second_number_show == 1 and mw.text.trim(second_number_show[1]) == '' then second_number_show = {} end 
	end
	
	if args['calculate'] then calculate_str = mw.text.trim(lib_calc._remove_strip_marker(args['calculate'])) end
	if args['calculate title'] or args['calculate_title'] then calculate_title = mw.text.trim(args['calculate title'] or args['calculate_title']) end
	if second_number_list == nil or (second_number_list and (second_number_list == {} or #second_number_list == 0) ) then
		second_number_list = first_number_list
	end
	if first_number_show == nil or (first_number_show and (first_number_show == {} or #first_number_show == 0) ) then
		first_number_show = first_number_list
	end
	if second_number_show == nil or (second_number_show and (second_number_show == {} or #second_number_show == 0) ) then
		second_number_show = first_number_show
	end
	local body, buffer_str, table_str = "", '', ''
	if comp_number == nil then comp_number = require("Module:Complex Number") end
    local cmath, qmath, bmath = comp_number.cmath.init(), comp_number.qmath.init(), comp_number.bmath.init()
    local mathtag = lib_calc.tagmath.init()
    
    local math_class = args['class']or''
    if mw.text.trim(math_class) == '' then math_class = "cmath" end
    local mymath = cmath
    local mytomath = cmath.toComplexNumber
    if mw.ustring.sub(math_class,1,7):upper()=="MODULE:" then
		local module_name, math_lib_name = lib_calc.checkModuleClass(math_class)
		xpcall(function()
			local load_module = require("Module:"..module_name)
			if load_module ~= nil then
				local load_math_lib = load_module[math_lib_name]
				if load_module ~= nil then
					local func_type = type(noop_func)
					local my_math_lib = (type(load_math_lib.init) == func_type) and load_math_lib.init() or load_math_lib
					if type(my_math_lib.constructor) == func_type then
						math_class = "mymath"
						mymath = my_math_lib
						mytomath = my_math_lib.constructor
					end
				end
			end
		end,noop_func)
	end
	body = body .. "! " .. (args["main head css"] or args["main_head_css"] or '') .. " | " .. calculate_title .. ' \n'
	for j=1,#second_number_list do
		local second_it = mw.text.trim(second_number_list[j] or '')
		local second_it_show = mw.text.trim(second_number_show[j] or '')
		if second_it ~= nil then
			local second_num_math = tostring(second_it_show or second_it)
			if should_math or can_math then 
				if args.class ~= "mathtag" and yesno(args['show math'] or args['show_math']) then
					second_num_math = lib_calc._re_math_output(second_num_math)
				end
				second_num_math = lib_calc._adj_math_output(second_num_math) 
			end
			if can_math then second_num_math = frame:callParserFunction{name = "#tag:math", args = {second_num_math}} end
			body = body .. "! " .. (args["head css"] or args["head_css"] or '') .. " | " .. second_num_math .. ' \n'
		end
	end
	body = body .. '\n'
	for i=1,#first_number_list do
		local first_it = mw.text.trim(first_number_list[i] or '')
		local first_it_show = mw.text.trim(first_number_show[i] or '')
		if first_it ~= nil then
			body = body .. "|-\n"; table_str = ''
			local first_num_math = tostring(first_it_show or first_it)
			if should_math or can_math then 
				if args.class ~= "mathtag" and yesno(args['show math'] or args['show_math']) then
					first_num_math = lib_calc._re_math_output(first_num_math)
				end
				first_num_math = lib_calc._adj_math_output(first_num_math) 
			end
			if can_math then first_num_math = frame:callParserFunction{name = "#tag:math", args = {first_num_math}} end
			body = body .. "! " .. (args["head css"] or args["head_css"] or '') .. " | "  .. first_num_math .. " \n"
			for j=1,#second_number_list do
				local second_it = mw.text.trim(second_number_list[j] or '')
				if second_it ~= nil then
					
					buffer_str = variable_process._getFormatingStringByArgument(calculate_str, {
						left=tostring(first_it),right=tostring(second_it)
					})
					local exec_result = lib_calc.calc( buffer_str or '', ( {
						cmath = cmath,
						qmath = qmath,
						bmath = bmath,
						mathtag = mathtag,
						mymath = mymath
					} ) [ math_class ] , (( {
						cmath = cmath.toComplexNumber,
						qmath = qmath.toQuaternionNumber,
						bmath = bmath.toBoolean,
						mathtag = mathtag.toTagMath,
						mymath = mytomath
					} ) [ math_class ] ) )
					local exec_result_str = mw.text.trim(tostring(exec_result) or '')
					local exec_check = mw.text.trim((tonumber(exec_result_str) and args[tonumber(exec_result_str)]) or args[exec_result_str] or '')
					if exec_check == '' then exec_check = nil end
					table_str = table_str .. '|' .. (exec_check or args["number css"] or args["number_css"] or '') .. "| "
					if should_math or can_math then
						if args.class ~= "mathtag" and yesno(args['show math'] or args['show_math']) then
							exec_result_str = lib_calc._re_math_output(exec_result_str)
						end
						exec_result_str = lib_calc._adj_math_output(exec_result_str) 
					end
					if can_math then exec_result_str = frame:callParserFunction{name = "#tag:math", args = {exec_result_str}} end
					table_str = table_str .. mw.text.trim(exec_result_str) .. ' \n'
				end
			end
			body = body .. table_str
		end
	end
	if use_ext_mathlib == true then TrackingCategory.append('使用擴充複變函數庫的頁面') end
	return body
end

function p._functionGraph(expr_arr,x_start,x_end,sampling, y_min, y_max, body_args,  math_lib, number_Constructer)
	if (yesno or require('Module:Yesno'))((body_args or {}).useOtherModule or 'no') == true then lib_calc.use_other_module = true end
	if comp_number == nil then comp_number = require("Module:Complex Number") end
	math = comp_number.math.init()
    lib_calc._randomseed()
	local mathlib, numberConstructer = math_lib or math, number_Constructer or tonumber
	local postfix = {}
	local check_func = {}
	local x_arr, y_arr = {}, {}
	if type(expr_arr) == type({}) then
		for i=1,#expr_arr do
			local local_func_sign = '↦'
			local check_parametric = mw.text.split(expr_arr[i],';')
			--解決 函數-參數式 衝突
			if mw.ustring.find(expr_arr[i], local_func_sign) then check_parametric = mw.text.split(expr_arr[i],'\\;')end
			if #check_parametric == 1 then
				local pre_expr, pre_scope = lib_calc._function_preprocessing(expr_arr[i], mathlib, numberConstructer, false)
				postfix[#postfix + 1] = lib_calc.infixToPostfix(pre_expr, debug_flag)
				if pre_scope then postfix[#postfix].scope = pre_scope end
			elseif #check_parametric >= 3 then
				postfix[#postfix + 1]={parametric=true}
				postfix[#postfix].x_name = check_parametric[1] or 't'
				postfix[#postfix].y_name = check_parametric[2] or 't'
				postfix[#postfix].x = lib_calc.infixToPostfix(check_parametric[1] or 't', debug_flag)
				postfix[#postfix].y = lib_calc.infixToPostfix(check_parametric[2] or 't', debug_flag)
				postfix[#postfix].t = mw.text.trim(check_parametric[3] or 't')
				postfix[#postfix].min = numberConstructer(check_parametric[4]) or numberConstructer(0)
				postfix[#postfix].max = numberConstructer(check_parametric[5]) or numberConstructer(1)
			end
			y_arr[#y_arr + 1] = {}
			x_arr[#x_arr + 1] = {}
		end
	else
		local local_func_sign = '↦'
		local check_parametric = mw.text.split(expr_arr,';')
		--解決 函數-參數式 衝突
		if mw.ustring.find(expr_arr, local_func_sign) then check_parametric = mw.text.split(expr_arr,'\\;')end
		
		if #check_parametric == 1 then
			local pre_expr, pre_scope = lib_calc._function_preprocessing(expr_arr, mathlib, numberConstructer, false)
			postfix[#postfix + 1] = lib_calc.infixToPostfix(pre_expr, debug_flag)
			if pre_scope then postfix[#postfix].scope = pre_scope end
		elseif #check_parametric >= 3 then
			postfix[#postfix + 1]={parametric=true}
			postfix[#postfix].x_name = check_parametric[1] or 't'
			postfix[#postfix].y_name = check_parametric[2] or 't'
			postfix[#postfix].x = lib_calc.infixToPostfix(check_parametric[1] or 't', debug_flag)
			postfix[#postfix].y = lib_calc.infixToPostfix(check_parametric[2] or 't', debug_flag)
			postfix[#postfix].t = check_parametric[3] or 't'
			postfix[#postfix].min = numberConstructer(check_parametric[4]) or numberConstructer(0)
			postfix[#postfix].max = numberConstructer(check_parametric[5]) or numberConstructer(1)
		end
		y_arr[#y_arr + 1] = {}
		x_arr[#x_arr + 1] = {}
	end
	local check_cexpr = mw.title.new("cexpr","template"):getContent()
	local check_isreal = mw.title.new("isReal","template"):getContent()
	local check_ifNumeric = mw.title.new("ifNumeric","template"):getContent()
	for i=0,sampling do
		local it = x_start + (i * (x_end-x_start) / sampling)
		local x_val = it
		for j=1,#expr_arr do
			local calc_val = " "
			xpcall(function() 
				if type(postfix[j]) == type({}) and postfix[j].parametric == true then
					local it_t = postfix[j].min + (i * (postfix[j].max - postfix[j].min) / sampling)
					--參數式
					calc_val = lib_calc.calc_by_postfix(postfix[j].y, {[postfix[j].t]=it_t}, mathlib, numberConstructer, false)
					x_val = lib_calc.calc_by_postfix(postfix[j].x, {[postfix[j].t]=it_t}, mathlib, numberConstructer, false)
				else
					calc_val = lib_calc.calc_by_postfix(postfix[j], {
						x=it,
						last=function(num) --for Template:數列
							local last_num = (body_args or {})['last' .. tonumber(tostring(num or 1))] or 0
							return numberConstructer(y_arr[j][#(y_arr[j])-(tonumber(tostring(num))or 1)+1] or last_num)
						end,
					}, mathlib, numberConstructer, false) 
					if( tonumber((body_args or {})["calc diff " .. tostring(j) ]) == 1 or ((yesno or require('Module:Yesno'))((body_args or {})["calc diff " .. tostring(j) ]or'no')==true) )then
						local dy = lib_calc.calc_by_postfix(postfix[j], {x=(it + 1e-6)}, mathlib, numberConstructer, false)
						calc_val = 1e6 * (dy - calc_val)
					end
				end
				if y_max and mathlib.re(calc_val) > y_max then calc_val = y_max end
				if y_min and mathlib.re(calc_val) < y_min then calc_val = y_min end
				if x_end and mathlib.re(x_val) > x_end then x_val = x_end end
				if x_start and mathlib.re(x_val) < x_start then x_val = x_start end
			end,function(_)end)
			if tonumber((body_args or {})["round number"]) ~= nil then 
				if calc_val then 
					calc_val = mathlib.round(calc_val, tonumber((body_args or {})["round number"]), 10)
				end
			end
			if tonumber((body_args or {})["nonreal is nan"]) == 1 then 
				if math.abs(tonumber(mathlib.abs(mathlib.nonRealPart(calc_val))) or 0) > 1e-14 then calc_val = nil end
			end
			local num_check = mw.ustring.lower(tostring(numberConstructer(calc_val)))
			if mw.ustring.match(num_check,"nan") or mw.ustring.match(num_check,"nil") or mw.ustring.match(num_check,"inf") then calc_val = ' ' end
			 num_check = mw.ustring.lower(tostring(numberConstructer(x_val)))
			if mw.ustring.match(num_check,"nan") or mw.ustring.match(num_check,"nil") or mw.ustring.match(num_check,"inf") then x_val = ' ' end
			y_arr[j][ (#(y_arr[j]) + 1) ] = tostring(calc_val)
			x_arr[j][ (#(x_arr[j]) + 1) ] = tostring(x_val)
		end
	end
	local result={}
	if #expr_arr > 0 then result.legend = "函數" end
	for i=1,#expr_arr do
		result['x'] = table.concat(x_arr[i],',')
		result['y' .. tostring(i)] = table.concat(y_arr[i],',')
		result['y' .. tostring(i) .. "Title"] = tostring( (body_args or {})[tostring(i) .. " name" ] or expr_arr[i] )
		if type(postfix[i]) == type({}) and postfix[i].parametric == true then
			result['y' .. tostring(i) .. "Title"] = "x=" .. postfix[i].x_name .. "; y=" .. postfix[i].y_name
		elseif( tonumber((body_args or {})["calc diff " .. tostring(i) ]) == 1 )then
			result['y' .. tostring(i) .. "Title"] = '( ' .. result['y' .. tostring(i) .. "Title"] .. " )\'"
		end
		
		if check_func[ result['y' .. tostring(i) .. "Title"] ] ~= nil then
			local new_name = result['y' .. tostring(i) .. "Title"] .. " ,(" .. tostring(check_func[ result['y' .. tostring(i) .. "Title"] ]+1) .. ")"
			check_func[ result['y' .. tostring(i) .. "Title"] ] = check_func[ result['y' .. tostring(i) .. "Title"] ] + 1
			result['y' .. tostring(i) .. "Title"] = new_name
		else check_func[ result['y' .. tostring(i) .. "Title"] ] = 1
		end
	end
	return result
end

function p.complex_graph(frame)
	if comp_number == nil then comp_number = require("Module:Complex Number") end
    local cmath, qmath = comp_number.cmath.init(), comp_number.qmath.init()
	if not getArgs then getArgs = require('Module:Arguments').getArgs end
	local args = getArgs(frame, {parentFirst=true})
	local expr = args[1] or args['1'] or 'x'
	local body_args, x_start, x_end, y_start, y_end, sampling, width = {}, -5, 5, -5, 5, 100, 120
	for arg_name, arg_value in pairs( args ) do
		local check_arg_name = mw.ustring.gsub(mw.ustring.lower(tostring(arg_name)), "[ _]", "")
		if check_arg_name == "xstart" then x_start = tonumber(arg_value)
		elseif check_arg_name == "xend" then x_end = tonumber(arg_value)
		elseif check_arg_name == "ystart" then y_start = tonumber(arg_value)
		elseif check_arg_name == "yend" then y_end = tonumber(arg_value)
		elseif check_arg_name == "sampling" then sampling = tonumber(arg_value)
		elseif check_arg_name == "width" then width = tonumber(arg_value)
		else body_args[arg_name] = arg_value
		end
	end
	local yesno = require('Module:Yesno')
	if yesno(args.useOtherModule or 'no') == true then lib_calc.use_other_module = true end
	
	local math_class = frame.args['class']or''
    local mymath = cmath
    local mytomath = cmath.toComplexNumber
    if mw.ustring.sub(math_class,1,7):upper()=="MODULE:" then
		local module_name, math_lib_name = lib_calc.checkModuleClass(math_class)
		xpcall(function()
			local load_module = require("Module:"..module_name)
			if load_module ~= nil then
				local load_math_lib = load_module[math_lib_name]
				if load_module ~= nil then
					local func_type = type(noop_func)
					local my_math_lib = (type(load_math_lib.init) == func_type) and load_math_lib.init() or load_math_lib
					if type(my_math_lib.constructor) == func_type then
						math_class = "mymath"
						mymath = my_math_lib
						mytomath = my_math_lib.constructor
					end
				end
			end
		end,noop_func)
	end
	
    local calc_result = p._complex_graph(expr,
    	x_start, x_end, y_start, y_end, sampling ,width, body_args, ( {
		cmath = cmath,
		qmath = qmath,
		mymath = mymath,
	} ) [math_class] , (( {
		cmath = cmath.toComplexNumber,
		qmath = qmath.toQuaternionNumber,
		mymath = mytomath,
	} ) [math_class] ) )
	local body = frame:callParserFunction{ name = '#tag:graph', args = {
    	mw.text.jsonEncode(calc_result)
	} }
	if use_ext_mathlib == true then TrackingCategory.append('使用擴充複變函數庫的頁面') end
	return body

end
function p._complex_graph(expr, x_start, x_end, y_start, y_end, sampling, width, body_args, math_lib, number_Constructer)
	if (yesno or require('Module:Yesno'))((body_args or {}).useOtherModule or 'no') == true then lib_calc.use_other_module = true end
	if comp_number == nil then comp_number = require("Module:Complex Number") end
	local cmath = comp_number.cmath.init()
	lib_calc._randomseed()
	local mathlib, numberConstructer = math_lib or cmath, number_Constructer or cmath.constructor
	
	local pxsize = width

	local pre_expr, pre_scope = lib_calc._function_preprocessing(expr, mathlib, numberConstructer, false)
	local postfix = lib_calc.infixToPostfix(pre_expr, debug_flag)
	if pre_scope then postfix.scope = pre_scope end

	local function HSBToRGB(h, s, b)
		local function k(n) return (n + h / 60) % 6 end
		local function f(n) return b * (1 - s * math.max(0, math.min(k(n), 4 - k(n), 1))) end
		return 255 * f(5), 255 * f(3), 255 * f(1);
	end

	local result = {}
	local i_value = mathlib.elements and (mathlib.elements[2] and mathlib.elements[2] or mathlib.i) or mathlib.i
	if not i_value then error("繪製失敗:所用數體無非實數單位元") end
	for i=0,sampling do
		local it_y = y_start + (i * (y_end-y_start) / sampling)
		local calc_row = {}
		for j=0,sampling-1 do
			local it_x = x_start + (j * (x_end-x_start) / sampling)
			local it = mathlib[0] + it_x + i_value * it_y
			local calc_val = lib_calc.calc_by_postfix(postfix, {x=it}, mathlib, numberConstructer, false)
			local check_nan = not not(tostring(calc_val):match("[%+%-]?[Nn][IiAa][LlNn]"))
			local check_inf = not not(tostring(calc_val):match("[%+%-]?[Ii][Nn][Ff]"))
			local result_color = "rgb(127,127,127)"
			if not check_nan then
				if check_inf then result_color = "rgb(255,255,255)"
				else
					local h, s, b = (mathlib.re(mathlib.arg(calc_val)) / math.pi * 180) % 360,
						mathlib.re(mathlib.inverse(mathlib.log(mathlib.abs(calc_val) + 1) * 0.3 + 1)),
						mathlib.re(-mathlib.inverse(mathlib.log(mathlib.abs(calc_val) + 1) * 5 + 1.1) + 1)
						result_color = string.format("rgb(%d,%d,%d)", HSBToRGB(h + ((h < 0)and 360 or 0), s, b))
				end
			end
			calc_row[#calc_row + 1] = result_color
		end
		result[#result + 1] = calc_row
	end
	local w_count = #result
	local cell_width = pxsize / (w_count+2)
	local graph_data = {
		width=pxsize,
		height=pxsize,
		data={},
		padding={left=cell_width,top=cell_width,bottom=cell_width,right=cell_width},
		scales={
		    {name="x",type="linear",domain={0,w_count},range="width"},
		    {name="y",type="linear",domain={0,w_count},range="height"},
		},
		marks={
		    {
		      type="rect",
		      from={data="funcdata"},
		      properties={
		        enter={
		          x={scale="x",field="0"},y={scale="y",field="1"},
		          width={value=cell_width},height={value=cell_width},
		          stroke={field="2"},
		          fill={field="2"},
		        }
		      }
		    }
		 }
	}
	local func_data = {name="funcdata",values={}}
	for x=1,w_count do
		for y=1,#(result[x]) do
			func_data.values[#(func_data.values)+1] = {x-1,#(result[x])-y+1,result[y][x]}
		end
	end
	graph_data.data[#(graph_data.data)+1] = func_data
	return graph_data
end


return p