跳转到内容

模組:沙盒/What7what8/func

本页使用了标题或全文手工转换
维基百科,自由的百科全书

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

-- SVG-based graph rendering functions
function p.drawFunctionGraphSVG(graph_data)
    local svg = mw.svg.new()
    local width = graph_data.width or 400
    local height = graph_data.height or 100
    
    -- Set up SVG attributes - convert numbers to strings
    svg:setAttribute('viewBox', '0 0 ' .. tostring(width) .. ' ' .. tostring(height))
    svg:setImgAttribute('width', tostring(width))
    svg:setImgAttribute('height', tostring(height))
    svg:setImgAttribute('class', 'function-graph-svg')
    
    -- Calculate padding for labels and titles
    local left_padding = 40   -- Space for y-axis labels
    local right_padding = 20
    local top_padding = 30    -- Space for title
    local bottom_padding = 40 -- Space for x-axis labels and title
    local content_width = width - left_padding - right_padding
    local content_height = height - top_padding - bottom_padding
    local content_x = left_padding
    local content_y = top_padding
    
    -- Create the content
    local content = ''
    
    -- Draw background for content area
    local background = mw.html.create('rect')
        :attr('x', tostring(content_x))
        :attr('y', tostring(content_y))
        :attr('width', tostring(content_width))
        :attr('height', tostring(content_height))
        :attr('fill', '#f8f8f8')
        :attr('stroke', '#e0e0e0')
        :attr('stroke-width', '1')
    
    content = content .. tostring(background)
    
    -- Calculate axis ranges
    local x_min = graph_data.x_min or 0
    local x_max = graph_data.x_max or 1
    local y_min = graph_data.y_min or 0
    local y_max = graph_data.y_max or 1
    
    -- Draw grid lines
    local x_divisions = 8
    local y_divisions = 8
    
    for i = 0, x_divisions do
        local x_pos = content_x + (i * content_width / x_divisions)
        local y_pos = content_y + (i * content_height / y_divisions)
        
        -- Vertical grid line
        local v_line = mw.html.create('line')
            :attr('x1', tostring(x_pos)):attr('y1', tostring(content_y))
            :attr('x2', tostring(x_pos)):attr('y2', tostring(content_y + content_height))
            :css('stroke', '#e8e8e8')
            :css('stroke-width', '1px')
        content = content .. tostring(v_line)
        
        -- Horizontal grid line
        local h_line = mw.html.create('line')
            :attr('x1', tostring(content_x)):attr('y1', tostring(y_pos))
            :attr('x2', tostring(content_x + content_width)):attr('y2', tostring(y_pos))
            :css('stroke', '#e8e8e8')
            :css('stroke-width', '1px')
        content = content .. tostring(h_line)
        
        -- X-axis labels
        local x_value = x_min + (i * (x_max - x_min) / x_divisions)
        local x_label = mw.html.create('text')
            :attr('x', tostring(x_pos))
            :attr('y', tostring(content_y + content_height + 15))
            :attr('text-anchor', 'middle')
            :attr('font-family', 'sans-serif')
            :attr('font-size', '10px')
            :attr('fill', '#333333')
            :wikitext(string.format('%.2f', x_value))
        content = content .. tostring(x_label)
        
        -- Y-axis labels
        local y_value = y_min + ((y_divisions - i) * (y_max - y_min) / y_divisions)
        local y_label = mw.html.create('text')
            :attr('x', tostring(content_x - 5))
            :attr('y', tostring(y_pos + 3))
            :attr('text-anchor', 'end')
            :attr('font-family', 'sans-serif')
            :attr('font-size', '10px')
            :attr('fill', '#333333')
            :wikitext(string.format('%.2f', y_value))
        content = content .. tostring(y_label)
    end
    
    -- Draw axes at y=0 and x=0 if within range
    local x_axis_y = content_y + content_height * (1 - (0 - y_min) / (y_max - y_min))
    local y_axis_x = content_x + content_width * ((0 - x_min) / (x_max - x_min))
    
    -- X-axis (y=0 line)
    if y_min <= 0 and y_max >= 0 then
        local x_axis = mw.html.create('line')
            :attr('x1', tostring(content_x))
            :attr('y1', tostring(x_axis_y))
            :attr('x2', tostring(content_x + content_width))
            :attr('y2', tostring(x_axis_y))
            :css('stroke', '#333333')
            :css('stroke-width', '1.5px')
        content = content .. tostring(x_axis)
    end
    
    -- Y-axis (x=0 line)
    if x_min <= 0 and x_max >= 0 then
        local y_axis = mw.html.create('line')
            :attr('x1', tostring(y_axis_x))
            :attr('y1', tostring(content_y))
            :attr('x2', tostring(y_axis_x))
            :attr('y2', tostring(content_y + content_height))
            :css('stroke', '#333333')
            :css('stroke-width', '1.5px')
        content = content .. tostring(y_axis)
    end
    
    -- Define colors for different series
    local colors = {'#ff4444', '#4444ff', '#44aa44', '#ffaa00', '#aa44aa', '#aa6644', 
                   '#44aaaa', '#aa4444', '#888844', '#aa88aa', '#4488aa', '#aa4488'}
    
    -- Draw each function series
    for i, series_data in ipairs(graph_data.series or {}) do
        local series_type = series_data.type or "function"
        local color = colors[(i-1) % #colors + 1]
        
        if series_type == "function" then
            -- Regular function - handle discontinuities properly
            local x_points = mw.text.split(series_data.x or '', ',')
            local y_points = mw.text.split(series_data.y or '', ',')
            
            if #x_points == #y_points and #x_points > 1 then
                -- Group continuous segments
                local segments = {}
                local current_segment = {}
                
                for j = 1, #x_points do
                    local x = tonumber(x_points[j])
                    local y = tonumber(y_points[j])
                    
                    if x and y and y ~= 0/0 and math.abs(y) < 1e100 then -- Valid point
                        -- Convert data coordinates to SVG coordinates
                        local svg_x = content_x + ((x - x_min) / (x_max - x_min)) * content_width
                        local svg_y = content_y + content_height - ((y - y_min) / (y_max - y_min)) * content_height
                        
                        if svg_x and svg_y and svg_x >= content_x and svg_x <= content_x + content_width and
                           svg_y >= content_y and svg_y <= content_y + content_height then
                            table.insert(current_segment, tostring(svg_x) .. ',' .. tostring(svg_y))
                        else
                            -- Point outside view - start new segment
                            if #current_segment > 1 then
                                table.insert(segments, current_segment)
                            end
                            current_segment = {}
                        end
                    else
                        -- Invalid point (NaN or infinity) - start new segment
                        if #current_segment > 1 then
                            table.insert(segments, current_segment)
                        end
                        current_segment = {}
                    end
                end
                
                -- Add the last segment if valid
                if #current_segment > 1 then
                    table.insert(segments, current_segment)
                end
                
                -- Draw each continuous segment separately
                for _, segment_points in ipairs(segments) do
                    if #segment_points > 1 then
                        local polyline = mw.html.create('polyline')
                            :css('stroke', color)
                            :attr('points', table.concat(segment_points, ' '))
                            :attr('fill', 'none')
                            :css('stroke-width', '2px')
                            :css('stroke-linejoin', 'round')
                            :css('stroke-linecap', 'round')
                        content = content .. tostring(polyline)
                    end
                end
            end
            
        elseif series_type == "parametric" then
            -- Parametric equation - use its own coordinate system
            local t_points = mw.text.split(series_data.t or '', ',')
            local x_points = mw.text.split(series_data.x or '', ',')
            local y_points = mw.text.split(series_data.y or '', ',')
            
            if #t_points == #x_points and #t_points == #y_points and #t_points > 1 then
                local points = {}
                
                for j = 1, #t_points do
                    local x = tonumber(x_points[j])
                    local y = tonumber(y_points[j])
                    
                    if x and y and y ~= 0/0 and math.abs(y) < 1e100 and x ~= 0/0 and math.abs(x) < 1e100 then
                        -- Convert parametric coordinates to SVG coordinates
                        local svg_x = content_x + ((x - x_min) / (x_max - x_min)) * content_width
                        local svg_y = content_y + content_height - ((y - y_min) / (y_max - y_min)) * content_height
                        
                        if svg_x and svg_y and svg_x >= content_x and svg_x <= content_x + content_width and
                           svg_y >= content_y and svg_y <= content_y + content_height then
                            table.insert(points, tostring(svg_x) .. ',' .. tostring(svg_y))
                        end
                    end
                end
                
                if #points > 1 then
                    local polyline = mw.html.create('polyline')
                        :css('stroke', color)
                        :attr('points', table.concat(points, ' '))
                        :attr('fill', 'none')
                        :css('stroke-width', '2px')
                        :css('stroke-linejoin', 'round')
                        :css('stroke-linecap', 'round')
                    content = content .. tostring(polyline)
                end
            end
        end
    end
    
    -- Add graph title
    if graph_data.title then
        local title = mw.html.create('text')
            :attr('x', tostring(width / 2))
            :attr('y', tostring(20))
            :attr('text-anchor', 'middle')
            :attr('font-family', 'sans-serif')
            :attr('font-size', '14px')
            :attr('font-weight', 'bold')
            :attr('fill', '#333333')
            :wikitext(graph_data.title)
        content = content .. tostring(title)
    end
    
    -- Add X-axis title
    if graph_data.x_label then
        local x_title = mw.html.create('text')
            :attr('x', tostring(content_x + content_width / 2))
            :attr('y', tostring(height - 10))
            :attr('text-anchor', 'middle')
            :attr('font-family', 'sans-serif')
            :attr('font-size', '12px')
            :attr('fill', '#333333')
            :wikitext(graph_data.x_label)
        content = content .. tostring(x_title)
    end
    
    -- Add Y-axis title
    if graph_data.y_label then
        local y_title = mw.html.create('text')
            :attr('x', tostring(10))
            :attr('y', tostring(content_y + content_height / 2))
            :attr('text-anchor', 'middle')
            :attr('font-family', 'sans-serif')
            :attr('font-size', '12px')
            :attr('fill', '#333333')
            :attr('transform', 'rotate(-90, 10, ' .. tostring(content_y + content_height / 2) .. ')')
            :wikitext(graph_data.y_label)
        content = content .. tostring(y_title)
    end
    
    -- Add legend
    if #(graph_data.series or {}) > 0 then
        local legend_x = content_x + content_width - 120
        local legend_y = content_y + 10
        local legend_bg = mw.html.create('rect')
            :attr('x', tostring(legend_x - 5))
            :attr('y', tostring(legend_y - 5))
            :attr('width', '130')
            :attr('height', tostring(20 + #graph_data.series * 20))
            :attr('fill', 'white')
            :attr('stroke', '#cccccc')
            :attr('stroke-width', '1')
            :attr('fill-opacity', '0.9')
        content = content .. tostring(legend_bg)
        
        local legend_title = mw.html.create('text')
            :attr('x', tostring(legend_x))
            :attr('y', tostring(legend_y + 10))
            :attr('font-family', 'sans-serif')
            :attr('font-size', '10px')
            :attr('font-weight', 'bold')
            :attr('fill', '#333333')
            :wikitext('Legend')
        content = content .. tostring(legend_title)
        
        for i, series_data in ipairs(graph_data.series) do
            local legend_item_y = legend_y + 15 + i * 15
            
            -- Color indicator
            local color_indicator = mw.html.create('rect')
                :attr('x', tostring(legend_x))
                :attr('y', tostring(legend_item_y - 6))
                :attr('width', '12')
                :attr('height', '3')
                :attr('fill', colors[(i-1) % #colors + 1])
            content = content .. tostring(color_indicator)
            
            -- Series label
            local series_label = series_data.title or 'Series ' .. tostring(i)
            local legend_label = mw.html.create('text')
                :attr('x', tostring(legend_x + 15))
                :attr('y', tostring(legend_item_y))
                :attr('font-family', 'sans-serif')
                :attr('font-size', '9px')
                :attr('fill', '#333333')
                :wikitext(series_label)
            content = content .. tostring(legend_label)
        end
    end
    
    svg:setContent(content)
    return svg:toImage()
end

function p.drawComplexGraphSVG(complex_data, width)
    local svg = mw.svg.new()
    local height = width -- Square aspect ratio for complex graphs
    
    svg:setAttribute('viewBox', '0 0 ' .. tostring(width) .. ' ' .. tostring(height))
    svg:setImgAttribute('width', tostring(width))
    svg:setImgAttribute('height', tostring(height))
    svg:setImgAttribute('class', 'complex-graph-svg')
    
    local content = ''
    
    -- Calculate padding for labels
    local padding = 30
    local content_size = width - 2 * padding
    
    -- Draw background
    local background = mw.html.create('rect')
        :attr('x', tostring(padding))
        :attr('y', tostring(padding))
        :attr('width', tostring(content_size))
        :attr('height', tostring(content_size))
        :attr('fill', '#f8f8f8')
        :attr('stroke', '#e0e0e0')
        :attr('stroke-width', '1')
    
    content = content .. tostring(background)
    
    -- Draw complex function visualization
    local pixel_size = content_size / #complex_data
    
    for i, row in ipairs(complex_data) do
        for j, color in ipairs(row) do
            local rect = mw.html.create('rect')
                :attr('x', tostring(padding + (j-1) * pixel_size))
                :attr('y', tostring(padding + (i-1) * pixel_size))
                :attr('width', tostring(pixel_size + 0.1)) -- Small overlap to avoid gaps
                :attr('height', tostring(pixel_size + 0.1))
                :attr('fill', color)
            content = content .. tostring(rect)
        end
    end
    
    -- Add title
    local title = mw.html.create('text')
        :attr('x', tostring(width / 2))
        :attr('y', tostring(20))
        :attr('text-anchor', 'middle')
        :attr('font-family', 'sans-serif')
        :attr('font-size', '14px')
        :attr('font-weight', 'bold')
        :attr('fill', '#333333')
        :wikitext('Complex Function Visualization')
    content = content .. tostring(title)
    
    -- Add axis labels
    local real_label = mw.html.create('text')
        :attr('x', tostring(width / 2))
        :attr('y', tostring(height - 10))
        :attr('text-anchor', 'middle')
        :attr('font-family', 'sans-serif')
        :attr('font-size', '12px')
        :attr('fill', '#333333')
        :wikitext('Real Axis')
    content = content .. tostring(real_label)
    
    local imag_label = mw.html.create('text')
        :attr('x', tostring(10))
        :attr('y', tostring(height / 2))
        :attr('text-anchor', 'middle')
        :attr('font-family', 'sans-serif')
        :attr('font-size', '12px')
        :attr('fill', '#333333')
        :attr('transform', 'rotate(-90, 10, ' .. tostring(height / 2) .. ')')
        :wikitext('Imaginary Axis')
    content = content .. tostring(imag_label)
    
    svg:setContent(content)
    return svg:toImage()
end

function p.applyFunctionGraph(frame)
    local working_frame = mw.getCurrentFrame()
    local graph_data = p._functionGraph({frame.args[1] or 'x'}, -10, 10, 50, nil, nil, {}, math, tonumber)
    graph_data.width = 400
    graph_data.height = 300
    graph_data.x_min = -10
    graph_data.x_max = 10
    graph_data.y_min = -2
    graph_data.y_max = 2
    graph_data.title = 'Function Graph'
    graph_data.x_label = 'x'
    graph_data.y_label = 'y'
    return p.drawFunctionGraphSVG(graph_data)
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
    
    -- Parse all arguments
    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 graph_data = 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] ) )
    
    -- Add dimensions and range info
    graph_data.width = body_args.width or 400
    graph_data.height = body_args.height or 300
    
    -- Use provided ranges or calculate from data
    graph_data.x_min = x_start
    graph_data.x_max = x_end
    
    if y_min == nil or y_max == nil then
        local calc_y_min, calc_y_max = 0, 1
        for i, series_data in ipairs(graph_data.series or {}) do
            local y_points = mw.text.split(series_data.y or '', ',')
            for _, point in ipairs(y_points) do
                local val = tonumber(point) or 0
                if val < calc_y_min then calc_y_min = val end
                if val > calc_y_max then calc_y_max = val end
            end
        end
        
        local y_range = calc_y_max - calc_y_min
        if y_range == 0 then y_range = 1 end
        graph_data.y_min = y_min or (calc_y_min - y_range * 0.1)
        graph_data.y_max = y_max or (calc_y_max + y_range * 0.1)
    else
        graph_data.y_min = y_min
        graph_data.y_max = y_max
    end
    
    -- Add titles and labels
    graph_data.title = body_args.title or body_args.caption or 'Function Graph'
    graph_data.x_label = body_args.x_label or 'x'
    graph_data.y_label = body_args.y_label or 'y'
    
    local body = p.drawFunctionGraphSVG(graph_data)
    
    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 result = {series = {}}
    local check_func = {}
    
    -- Process each expression
    for i, expr in ipairs(expr_arr) do
        local local_func_sign = '↦'
        local check_parametric = mw.text.split(expr,';')
        
        -- Check if this is a parametric equation
        if mw.ustring.find(expr, local_func_sign) then 
            check_parametric = mw.text.split(expr,'\\;')
        end
        
        if #check_parametric == 1 then
            -- Regular function
            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 x_points, y_points = {}, {}
            
            for j=0,sampling do
                local it = x_start + (j * (x_end-x_start) / sampling)
                local calc_val = " "
                
                xpcall(function() 
                    calc_val = lib_calc.calc_by_postfix(postfix, {
                        x=it,
                        last=function(num)
                            local last_num = (body_args or {})['last' .. tonumber(tostring(num or 1))] or 0
                            return numberConstructer((#y_points > 0 and y_points[#y_points-(tonumber(tostring(num))or 1)+1]) or last_num)
                        end,
                    }, mathlib, numberConstructer, false)
                    
                    if( tonumber((body_args or {})["calc diff " .. tostring(i) ]) == 1 or ((yesno or require('Module:Yesno'))((body_args or {})["calc diff " .. tostring(i) ]or'no')==true) )then
                        local dy = lib_calc.calc_by_postfix(postfix, {x=(it + 1e-6)}, mathlib, numberConstructer, false)
                        calc_val = 1e6 * (dy - calc_val)
                    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
                
                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 = nil 
                end
                
                table.insert(x_points, tostring(it))
                table.insert(y_points, tostring(calc_val or 0/0)) -- Use NaN for invalid points
            end
            
            local series_title = tostring( (body_args or {})[tostring(i) .. " name" ] or expr )
            if( tonumber((body_args or {})["calc diff " .. tostring(i) ]) == 1 )then
                series_title = '( ' .. series_title .. " )'"
            end
            
            if check_func[series_title] ~= nil then
                local new_name = series_title .. " ,(" .. tostring(check_func[series_title]+1) .. ")"
                check_func[series_title] = check_func[series_title] + 1
                series_title = new_name
            else 
                check_func[series_title] = 1
            end
            
            table.insert(result.series, {
                type = "function",
                x = table.concat(x_points, ','),
                y = table.concat(y_points, ','),
                title = series_title
            })
            
        elseif #check_parametric >= 3 then
            -- Parametric equation
            local x_name = check_parametric[1] or 't'
            local y_name = check_parametric[2] or 't'
            local t_var = mw.text.trim(check_parametric[3] or 't')
            local t_min = numberConstructer(check_parametric[4]) or numberConstructer(0)
            local t_max = numberConstructer(check_parametric[5]) or numberConstructer(1)
            
            local x_expr = lib_calc.infixToPostfix(x_name, debug_flag)
            local y_expr = lib_calc.infixToPostfix(y_name, debug_flag)
            
            local t_points, x_points, y_points = {}, {}, {}
            
            for j=0,sampling do
                local t_val = t_min + (j * (t_max - t_min) / sampling)
                local x_val, y_val = " ", " "
                
                xpcall(function() 
                    x_val = lib_calc.calc_by_postfix(x_expr, {[t_var]=t_val}, mathlib, numberConstructer, false)
                    y_val = lib_calc.calc_by_postfix(y_expr, {[t_var]=t_val}, mathlib, numberConstructer, false)
                end, function(_) end)
                
                local x_check = mw.ustring.lower(tostring(numberConstructer(x_val)))
                local y_check = mw.ustring.lower(tostring(numberConstructer(y_val)))
                
                if mw.ustring.match(x_check,"nan") or mw.ustring.match(x_check,"nil") or mw.ustring.match(x_check,"inf") then 
                    x_val = nil 
                end
                if mw.ustring.match(y_check,"nan") or mw.ustring.match(y_check,"nil") or mw.ustring.match(y_check,"inf") then 
                    y_val = nil 
                end
                
                table.insert(t_points, tostring(t_val))
                table.insert(x_points, tostring(x_val or 0/0))
                table.insert(y_points, tostring(y_val or 0/0))
            end
            
            local series_title = "x=" .. x_name .. "; y=" .. y_name
            if check_func[series_title] ~= nil then
                local new_name = series_title .. " ,(" .. tostring(check_func[series_title]+1) .. ")"
                check_func[series_title] = check_func[series_title] + 1
                series_title = new_name
            else 
                check_func[series_title] = 1
            end
            
            table.insert(result.series, {
                type = "parametric",
                t = table.concat(t_points, ','),
                x = table.concat(x_points, ','),
                y = table.concat(y_points, ','),
                title = series_title
            })
        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 = p.drawComplexGraphSVG(calc_result, width)
    
    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
    return result
end

return p