跳转到内容

模組:沙盒/a2569875/PythonScript/ExtraLibrarys/Matplotlib

维基百科,自由的百科全书
-- Module:PythonScript/ExtraLibrarys/Matplotlib
-- Python matplotlib / matplotlib.pyplot / mpl_toolkits.mplot3d 前端 API 與狀態機
-- 串接 SVGRenderer、MathEngine、RendererUtils 與 Style.css
local Matplotlib = {}

local PyClass = require("Module:沙盒/a2569875/PythonScript/Class")
local Runtime = require("Module:沙盒/a2569875/PythonScript/Runtime")
local RUtils = require("Module:沙盒/a2569875/PythonScript/Runtime/Utils")
local RendererUtils = require("Module:沙盒/a2569875/PythonScript/ExtraLibrarys/Matplotlib/RendererUtils")
local MathEngine = require("Module:沙盒/a2569875/PythonScript/ExtraLibrarys/Matplotlib/MathEngine")
local SVGRenderer = require("Module:沙盒/a2569875/PythonScript/ExtraLibrarys/Matplotlib/SVGRenderer")

local B = Runtime.builtins
local floor, ceil, max, min, abs, sqrt = math.floor, math.ceil, math.max, math.min, math.abs, math.sqrt

-- ==========================================
-- 1. Python 與 Lua 資料結構轉換輔助工具
-- ==========================================
local function is_none(v)
	return v == nil or v == B.None or (type(v) == "table" and v.__class and v.__class.__name == "NoneType")
end

local function unwrap_scalar(v)
	if is_none(v) then return nil end
	if type(v) == "number" or type(v) == "string" or type(v) == "boolean" then return v end
	if type(v) == "table" then
		if v.__type == "int" then return tonumber(v.value) or 0 end
		if v.__type == "instance" and v.__builtin_value ~= nil then
			return unwrap_scalar(v.__builtin_value)
		end
	end
	return v
end

-- 將 Python dict 轉為純 Lua table
local function py_dict_to_lua(d)
	if type(d) ~= "table" or is_none(d) then return {} end
	if d.__type == "dict" and d.pairs then
		local out = {}
		for _, p in ipairs(d.pairs) do
			out[unwrap_scalar(p.key)] = unwrap_scalar(p.value)
		end
		return out
	elseif d.__type == "instance" and d.__builtin_value and d.__builtin_value.__type == "dict" then
		return py_dict_to_lua(d.__builtin_value)
	elseif d.__type == nil then
		return d
	end
	return {}
end

-- 解包 1D 任意型別陣列 (保留字串與數值,支援 list, tuple, NumPy ndarray)
local function to_raw_array(obj)
	if is_none(obj) then return {} end
	if type(obj) ~= "table" then return { obj } end
	if obj.__type == nil and #obj > 0 then return obj end
	if obj.elements then
		local out = {}
		for i = 1, #obj.elements do
			out[i] = unwrap_scalar(obj.elements[i])
		end
		return out
	end
	if obj.__type == "range" then
		local out = {}
		local cur, stop, step = obj.start, obj.stop, obj.step
		if step > 0 then
			while cur < stop do out[#out + 1] = cur; cur = cur + step end
		else
			while cur > stop do out[#out + 1] = cur; cur = cur + step end
		end
		return out
	end
	if obj.__type == "instance" and obj.__dict then
		local d = obj.__dict
		if type(d._data) == "table" then
			local out = {}
			for i = 1, #d._data do out[i] = unwrap_scalar(d._data[i]) end
			return out
		elseif type(d.data) == "table" and d.data.elements then
			return to_raw_array(d.data)
		end
		local ok, tolist_m = pcall(PyClass.get_attr, obj, "tolist")
		if ok and tolist_m then
			local ok2, lst = pcall(require("Module:沙盒/a2569875/PythonScript/Evaluator").invoke, tolist_m, {}, {})
			if ok2 and type(lst) == "table" then return to_raw_array(lst) end
		end
	end
	return {}
end

local function to_string_array(obj)
	local raw = to_raw_array(obj)
	local out = {}
	for i = 1, #raw do
		out[i] = RUtils.to_str(raw[i], false)
	end
	return out
end

local function to_num_array(obj)
	if is_none(obj) then return {} end
	if type(obj) == "number" then return { obj } end
	if type(obj) == "table" and obj.__type == "int" then return { tonumber(obj.value) or 0 } end
	local raw = to_raw_array(obj)
	local out = {}
	for i = 1, #raw do
		out[i] = MathEngine.unwrap_number(raw[i])
	end
	return out
end

-- 處理類別軸座標:若傳入字串陣列 (如 ['A', 'B', 'C']),自動映射為 1..N 並回傳對應標籤
local function resolve_coord_or_categories(arr_in)
	local raw = to_raw_array(arr_in)
	local has_str = false
	for i = 1, #raw do
		if type(raw[i]) == "string" and tonumber(raw[i]) == nil then
			has_str = true
			break
		end
	end
	if not has_str then
		return to_num_array(arr_in), nil
	end
	local nums, cats = {}, {}
	for i = 1, #raw do
		nums[i] = i - 1
		cats[i] = tostring(raw[i])
	end
	return nums, cats
end

-- 解析誤差陣列 (支援單一純量、1D 對稱陣列或 2xN 非對稱陣列)
local function parse_error_bars(err_in, n)
	if is_none(err_in) then return nil, nil end
	if type(err_in) == "number" then
		local lo, hi = {}, {}
		for i = 1, n do lo[i], hi[i] = err_in, err_in end
		return lo, hi
	end
	local grid = MathEngine.to2DGrid(err_in)
	if #grid == 2 and type(grid[1]) == "table" and #grid[1] == n and n > 1 then
		return grid[1], grid[2]
	end
	local arr = to_num_array(err_in)
	return arr, arr
end

local function fn(name, func)
	return { __type = "builtin_method", __name = name, __call = func }
end

-- ==========================================
-- 2. Axes、Figure、BarPatch、AxisArtist、Colormap 與 Ticker 類別定義
-- ==========================================
local AxesClass = nil
local FigureClass = nil
local BarPatchClass = nil
local AxisArtistClass = nil
local ColormapClass = nil

-- 建立單一長條/扇形 Patch 實例 (支援 bar.set_facecolor, bar.set_alpha, bar.set_edgecolor)
local function make_bar_patch(series_tbl, bar_idx)
	if not BarPatchClass then
		local bp_dict = {
			set_facecolor = fn("set_facecolor", function(a)
				local self = a[1]
				self.__dict._series.bar_colors[self.__dict._idx] = unwrap_scalar(a[2])
				return B.None
			end),
			set_edgecolor = fn("set_edgecolor", function(a)
				local self = a[1]
				self.__dict._series.bar_edgecolors[self.__dict._idx] = unwrap_scalar(a[2])
				return B.None
			end),
			set_alpha = fn("set_alpha", function(a)
				local self = a[1]
				self.__dict._series.bar_alphas[self.__dict._idx] = MathEngine.unwrap_number(a[2])
				return B.None
			end),
			set_linewidth = fn("set_linewidth", function(a)
				local self = a[1]
				self.__dict._series.bar_linewidths[self.__dict._idx] = MathEngine.unwrap_number(a[2])
				return B.None
			end),
			get_height = fn("get_height", function(a)
				local self = a[1]
				return self.__dict._series.height[self.__dict._idx] or 0
			end),
			get_width = fn("get_width", function(a)
				local self = a[1]
				local w = self.__dict._series.width
				return (type(w) == "table") and (w[self.__dict._idx] or 0.8) or (tonumber(w) or 0.8)
			end),
			get_x = fn("get_x", function(a)
				local self = a[1]
				return self.__dict._series.x[self.__dict._idx] or 0
			end)
		}
		bp_dict.set_color = bp_dict.set_facecolor
		bp_dict.set_lw = bp_dict.set_linewidth
		BarPatchClass = PyClass.make_class("Rectangle", { B.object }, bp_dict)
	end
	local inst = PyClass.make_instance(BarPatchClass)
	inst.__dict._series = series_tbl
	inst.__dict._idx = bar_idx
	return inst
end

-- 建立 ax.xaxis / ax.yaxis 物件 (支援 set_major_locator 與 set_major_formatter)
local function make_axis_artist(ax_state, kind)
	if not AxisArtistClass then
		local aa_dict = {
			set_major_locator = fn("set_major_locator", function(a)
				local self = a[1]
				local loc = a[2]
				local st = self.__dict._ax_state
				local k = self.__dict._kind
				if type(loc) == "table" and loc.__dict then
					local lkind = loc.__dict._kind
					if lkind == "MultipleLocator" then
						local step = tonumber(loc.__dict.base)
						if k == "x" then st.x_tick_step = step
						elseif k == "y" then st.y_tick_step = step
						else st.z_tick_step = step end
					elseif lkind == "FixedLocator" then
						if k == "x" then st.xticks = loc.__dict.locs
						elseif k == "y" then st.yticks = loc.__dict.locs
						else st.zticks = loc.__dict.locs end
					elseif lkind == "NullLocator" then
						if k == "x" then st.xticks = {}; st.hide_xtick_labels = true
						elseif k == "y" then st.yticks = {}; st.hide_ytick_labels = true end
					end
				end
				return B.None
			end),
			set_major_formatter = fn("set_major_formatter", function(a)
				local self = a[1]
				local fmt = a[2]
				local st = self.__dict._ax_state
				local k = self.__dict._kind
				if type(fmt) == "table" and fmt.__dict then
					local fkind = fmt.__dict._kind
					if fkind == "NullFormatter" then
						if k == "x" then st.hide_xtick_labels = true
						elseif k == "y" then st.hide_ytick_labels = true end
					elseif fkind == "FormatStrFormatter" then
						if k == "x" then st.x_format = fmt.__dict.fmt
						elseif k == "y" then st.y_format = fmt.__dict.fmt end
					end
				end
				return B.None
			end)
		}
		aa_dict.set_minor_locator = aa_dict.set_major_locator
		aa_dict.set_minor_formatter = aa_dict.set_major_formatter
		AxisArtistClass = PyClass.make_class("Axis", { B.object }, aa_dict)
	end
	local inst = PyClass.make_instance(AxisArtistClass)
	inst.__dict._ax_state = ax_state
	inst.__dict._kind = kind
	return inst
end

-- 建立可呼叫的 Colormap 物件:既可直接呼叫 colormap(0.5) 回傳色碼,也可當作 cmap=colormap 傳遞
local function make_colormap_obj(cname)
	return {
		__type = "builtin_method",
		__name = cname,
		_cmap_name = cname,
		__call = function(a)
			local val = MathEngine.unwrap_number(a and a[1]) or 0
			return RendererUtils.sampleColormap(cname, val, 1.0)
		end
	}
end

local function resolve_cmap_arg(v, fallback)
	if is_none(v) then return fallback end
	if type(v) == "table" and v._cmap_name then return v._cmap_name end
	return RUtils.to_str(v, false)
end

local function next_line_color(ax_state)
	local pal = ax_state.palette or RendererUtils.DEFAULT_COLORS
	ax_state._line_color_idx = (ax_state._line_color_idx or 0) + 1
	return pal[((ax_state._line_color_idx - 1) % #pal) + 1]
end

local function next_patch_color(ax_state)
	local pal = ax_state.palette or RendererUtils.DEFAULT_COLORS
	ax_state._patch_color_idx = (ax_state._patch_color_idx or 0) + 1
	return pal[((ax_state._patch_color_idx - 1) % #pal) + 1]
end

local function extract_common_style(kw, fmt_str, ax_state, is_patch)
	kw = kw or {}
	local f_col, f_ls, f_mk = RendererUtils.parseFmtString(fmt_str)
	local col = kw.color or kw.c or kw.facecolor or kw.fc or f_col
	if is_none(col) then
		col = is_patch and next_patch_color(ax_state) or next_line_color(ax_state)
	elseif type(col) == "table" and col.elements then
		-- 若為顏色列表 (如 bar 的每條不同色)
		local c_list = {}
		for _, el in ipairs(col.elements) do c_list[#c_list + 1] = unwrap_scalar(el) end
		col = c_list
	end

	local ls = kw.linestyle or kw.ls or f_ls or "-"
	local mk = kw.marker or f_mk
	local lw = kw.linewidth or kw.lw or kw.linewidths
	if not is_none(lw) then lw = MathEngine.unwrap_number(lw) else lw = nil end
	local ms = kw.markersize or kw.ms
	if not is_none(ms) then ms = MathEngine.unwrap_number(ms) else ms = nil end
	local alpha = kw.alpha
	if not is_none(alpha) then alpha = MathEngine.unwrap_number(alpha) else alpha = nil end
	local label = (not is_none(kw.label)) and RUtils.to_str(kw.label, false) or nil
	local ecol = kw.edgecolor or kw.ec or kw.edgecolors
	if not is_none(ecol) then ecol = RUtils.to_str(ecol, false) else ecol = nil end

	return {
		color = col,
		linestyle = ls,
		marker = mk,
		linewidth = lw,
		markersize = ms,
		alpha = alpha,
		label = label,
		edgecolor = ecol
	}
end

local function get_axes_class()
	if AxesClass then return AxesClass end

	local axes_dict = {
		-- (1) 折線圖與 3D 空間曲線 (ax.plot / ax.plot3D)
		plot = fn("plot", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}

			-- 若在 3D 座標系且傳入 (x, y, z)
			if st.projection == "3d" and #a >= 4 and type(a[4]) ~= "string" then
				local xs = to_num_array(a[2])
				local ys = to_num_array(a[3])
				local zs = to_num_array(a[4])
				local fmt_s = (type(a[5]) == "string") and a[5] or nil
				local style = extract_common_style(kw, fmt_s, st, false)
				local pts = {}
				for i = 1, min(#xs, min(#ys, #zs)) do
					pts[i] = { xs[i], ys[i], zs[i] }
				end
				table.insert(st.series, {
					kind = "curve3d",
					points = pts,
					color = style.color,
					linestyle = style.linestyle,
					linewidth = style.linewidth or 2.2,
					marker = style.marker,
					markersize = style.markersize,
					alpha = style.alpha,
					label = style.label
				})
				return { __type = "list", elements = { self } }
			end

			-- 2D / Polar 折線:支援 plot(y), plot(x, y), plot(x, y, fmt) 與多組連傳
			local idx = 2
			local n_args = #a
			while idx <= n_args do
				local x_arr, y_arr, fmt_s
				if idx == n_args then
					y_arr = to_num_array(a[idx])
					x_arr = {}
					for i = 1, #y_arr do x_arr[i] = i - 1 end
					idx = idx + 1
				elseif type(a[idx + 1]) == "string" then
					y_arr = to_num_array(a[idx])
					x_arr = {}
					for i = 1, #y_arr do x_arr[i] = i - 1 end
					fmt_s = a[idx + 1]
					idx = idx + 2
				else
					local cats
					x_arr, cats = resolve_coord_or_categories(a[idx])
					if cats then st.xticks = x_arr; st.xticklabels = cats end
					y_arr = to_num_array(a[idx + 1])
					if idx + 2 <= n_args and type(a[idx + 2]) == "string" then
						fmt_s = a[idx + 2]
						idx = idx + 3
					else
						idx = idx + 2
					end
				end

				local style = extract_common_style(kw, fmt_s, st, false)
				table.insert(st.series, {
					kind = "line",
					x = x_arr,
					y = y_arr,
					polar = (st.projection == "polar") or (kw.polar == true),
					color = style.color,
					linestyle = style.linestyle,
					linewidth = style.linewidth,
					marker = style.marker,
					markersize = style.markersize,
					alpha = style.alpha,
					label = style.label,
					interpolate = kw.interpolate,
					jumps = kw.jumps and to_num_array(kw.jumps) or nil,
					mark_jumps = kw.mark_jumps or kw.markJumps,
					jump_threshold = kw.jump_threshold or kw.jumpThreshold,
					jump_type = kw.jump_type or kw.jumpType,
					show_mark_values = kw.show_mark_values or kw.showMarkValues,
					mark_value_format = kw.mark_value_format or kw.markValueFormat,
					mark_value_dir = kw.mark_value_dir or kw.markValueDir
				})
			end
			return { __type = "list", elements = { self } }
		end),

		-- (2) 階梯圖 (ax.step)
		step = fn("step", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local x_arr, cats = resolve_coord_or_categories(a[2])
			if cats then st.xticks = x_arr; st.xticklabels = cats end
			local y_arr = to_num_array(a[3])
			local fmt_s = (type(a[4]) == "string") and a[4] or nil
			local where_mode = (kw.where and tostring(kw.where)) or "pre"
			local interp = "step-" .. where_mode
			local style = extract_common_style(kw, fmt_s, st, false)
			table.insert(st.series, {
				kind = "line",
				x = x_arr,
				y = y_arr,
				color = style.color,
				linestyle = style.linestyle,
				linewidth = style.linewidth,
				marker = style.marker,
				markersize = style.markersize,
				alpha = style.alpha,
				label = style.label,
				interpolate = interp,
				mark_jumps = kw.mark_jumps or kw.markJumps,
				jump_threshold = kw.jump_threshold or kw.jumpThreshold
			})
			return { __type = "list", elements = { self } }
		end),

		-- (3) 散佈圖 (ax.scatter / ax.scatter3D)
		scatter = fn("scatter", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}

			if st.projection == "3d" and #a >= 4 then
				local xs = to_num_array(a[2])
				local ys = to_num_array(a[3])
				local zs = to_num_array(a[4])
				local s_in = a[5] or kw.s
				local c_in = a[6] or kw.c or kw.color
				local pts = {}
				for i = 1, min(#xs, min(#ys, #zs)) do pts[i] = { xs[i], ys[i], zs[i] } end
				local c_arr = (type(c_in) == "table") and to_raw_array(c_in) or nil
				local col = (not c_arr and not is_none(c_in)) and RUtils.to_str(c_in, false) or next_line_color(st)
				local s_arr = (type(s_in) == "table") and to_num_array(s_in) or nil
				local ms = (not s_arr and not is_none(s_in)) and max(1.8, sqrt(MathEngine.unwrap_number(s_in)) * 0.58) or 3.8
				table.insert(st.series, {
					kind = "scatter3d",
					points = pts,
					color = col,
					c_array = c_arr,
					s_array = s_arr,
					markersize = ms,
					marker = (not is_none(kw.marker)) and RUtils.to_str(kw.marker, false) or "circle",
					cmap = resolve_cmap_arg(kw.cmap, nil),
					alpha = (not is_none(kw.alpha)) and MathEngine.unwrap_number(kw.alpha) or nil,
					edgecolor = (not is_none(kw.edgecolor or kw.edgecolors)) and RUtils.to_str(kw.edgecolor or kw.edgecolors, false) or nil,
					linewidth = (not is_none(kw.linewidth or kw.linewidths)) and MathEngine.unwrap_number(kw.linewidth or kw.linewidths) or nil,
					label = (not is_none(kw.label)) and RUtils.to_str(kw.label, false) or nil
				})
				return self
			end

			local x_arr, cats = resolve_coord_or_categories(a[2])
			if cats then st.xticks = x_arr; st.xticklabels = cats end
			local y_arr = to_num_array(a[3])
			local s_in = a[4] or kw.s
			local c_in = a[5] or kw.c or kw.color
			local c_arr = (type(c_in) == "table") and to_raw_array(c_in) or nil
			local col = (not c_arr and not is_none(c_in)) and RUtils.to_str(c_in, false) or next_line_color(st)
			local s_arr = (type(s_in) == "table") and to_num_array(s_in) or nil
			local ms = (not s_arr and not is_none(s_in)) and max(1.8, sqrt(MathEngine.unwrap_number(s_in)) * 0.58) or (kw.markersize and MathEngine.unwrap_number(kw.markersize)) or 3.8

			table.insert(st.series, {
				kind = "scatter",
				x = x_arr,
				y = y_arr,
				polar = (st.projection == "polar") or (kw.polar == true),
				color = col,
				c_array = c_arr,
				s_array = s_arr,
				markersize = ms,
				marker = (not is_none(kw.marker)) and RUtils.to_str(kw.marker, false) or "circle",
				cmap = resolve_cmap_arg(kw.cmap, nil),
				alpha = (not is_none(kw.alpha)) and MathEngine.unwrap_number(kw.alpha) or nil,
				edgecolor = (not is_none(kw.edgecolor or kw.edgecolors)) and RUtils.to_str(kw.edgecolor or kw.edgecolors, false) or nil,
				linewidth = (not is_none(kw.linewidth or kw.linewidths)) and MathEngine.unwrap_number(kw.linewidth or kw.linewidths) or nil,
				label = (not is_none(kw.label)) and RUtils.to_str(kw.label, false) or nil,
				show_mark_values = kw.show_mark_values or kw.showMarkValues
			})
			return self
		end),

		-- (4) 多邊形填色與雷達圖填色 (ax.fill)
		fill = fn("fill", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local x_arr = to_num_array(a[2])
			local y_arr = to_num_array(a[3])
			local fmt_s = (type(a[4]) == "string") and a[4] or nil
			local style = extract_common_style(kw, fmt_s, st, true)
			table.insert(st.series, {
				kind = "fill",
				x = x_arr,
				y = y_arr,
				polar = (st.projection == "polar") or (kw.polar == true),
				color = style.color,
				edgecolor = style.edgecolor,
				linewidth = style.linewidth,
				alpha = style.alpha or 0.28,
				label = style.label
			})
			return { __type = "list", elements = { self } }
		end),

		-- (5) 面積圖 (ax.fill_between)
		fill_between = fn("fill_between", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local x_arr = to_num_array(a[2])
			local y1_arr = to_num_array(a[3])
			local y2_in = a[4] or kw.y2 or 0
			local y2_val = (type(y2_in) == "table") and to_num_array(y2_in) or (MathEngine.unwrap_number(y2_in) or 0)
			local where_arr = (not is_none(a[5] or kw.where)) and to_raw_array(a[5] or kw.where) or nil
			local style = extract_common_style(kw, nil, st, true)
			table.insert(st.series, {
				kind = "fill_between",
				x = x_arr,
				y1 = y1_arr,
				y2 = y2_val,
				where = where_arr,
				color = style.color,
				alpha = style.alpha or 0.35,
				label = style.label
			})
			return self
		end),

		-- (6) 堆疊面積圖 (ax.stackplot)
		stackplot = fn("stackplot", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local x_arr = to_num_array(a[2])
			local n = #x_arr
			local layers = {}
			if #a == 3 and type(a[3]) == "table" then
				local grid = MathEngine.to2DGrid(a[3])
				if #grid > 0 and type(grid[1]) == "table" and #grid[1] == n then
					layers = grid
				else
					layers = { to_num_array(a[3]) }
				end
			else
				for i = 3, #a do
					layers[#layers + 1] = to_num_array(a[i])
				end
			end

			local labels = (not is_none(kw.labels)) and to_string_array(kw.labels) or {}
			local colors = (not is_none(kw.colors)) and to_string_array(kw.colors) or nil
			local alpha = (not is_none(kw.alpha)) and MathEngine.unwrap_number(kw.alpha) or 0.78

			local prev_y = {}
			for j = 1, n do prev_y[j] = 0 end

			for k, lyr in ipairs(layers) do
				local next_y = {}
				for j = 1, n do
					next_y[j] = prev_y[j] + (tonumber(lyr[j]) or 0)
				end
				local col = (colors and colors[k]) or next_patch_color(st)
				table.insert(st.series, {
					kind = "fill_between",
					x = x_arr,
					y1 = next_y,
					y2 = prev_y,
					color = col,
					alpha = alpha,
					label = labels[k]
				})
				prev_y = next_y
			end
			return { __type = "list", elements = { self } }
		end),

		-- (7) 垂直長條圖與水平長條圖 / 甘特圖 (ax.bar / ax.barh / ax.broken_barh)
		bar = fn("bar", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local x_arr, cats = resolve_coord_or_categories(a[2] or kw.x)
			if cats then st.xticks = x_arr; st.xticklabels = cats end
			if not is_none(kw.tick_label) then
				st.xticks = x_arr
				st.xticklabels = to_string_array(kw.tick_label)
			end
			local h_arr = to_num_array(a[3] or kw.height)
			local w_in = a[4] or kw.width or 0.8
			local w_val = (type(w_in) == "table") and to_num_array(w_in) or (MathEngine.unwrap_number(w_in) or 0.8)
			local b_in = a[5] or kw.bottom or 0
			local b_val = (type(b_in) == "table") and to_num_array(b_in) or (MathEngine.unwrap_number(b_in) or 0)
			local style = extract_common_style(kw, nil, st, true)

			local default_align = (st.projection == "polar" or kw.polar == true) and "edge" or "center"
			local bar_series = {
				kind = "bar",
				x = x_arr,
				height = h_arr,
				width = w_val,
				bottom = b_val,
				align = (not is_none(kw.align)) and RUtils.to_str(kw.align, false) or default_align,
				color = style.color,
				edgecolor = style.edgecolor,
				linewidth = style.linewidth,
				alpha = style.alpha,
				label = style.label,
				bar_colors = {},
				bar_alphas = {},
				bar_edgecolors = {},
				bar_linewidths = {}
			}
			table.insert(st.series, bar_series)

			if not is_none(kw.yerr) or not is_none(kw.xerr) then
				local top_y = {}
				for i = 1, #h_arr do
					local bi = (type(b_val) == "table") and (b_val[i] or 0) or b_val
					top_y[i] = bi + (h_arr[i] or 0)
				end
				local ylo, yhi = parse_error_bars(kw.yerr, #x_arr)
				local xlo, xhi = parse_error_bars(kw.xerr, #x_arr)
				table.insert(st.series, {
					kind = "errorbar",
					x = x_arr, y = top_y,
					yerr_low = ylo, yerr_high = yhi,
					xerr_low = xlo, xerr_high = xhi,
					linestyle = "none", marker = "none",
					ecolor = (not is_none(kw.ecolor)) and RUtils.to_str(kw.ecolor, false) or "#222222",
					capsize = (not is_none(kw.capsize)) and MathEngine.unwrap_number(kw.capsize) or 3.5
				})
			end

			local patches = {}
			for idx = 1, min(#x_arr, #h_arr) do
				patches[idx] = make_bar_patch(bar_series, idx)
			end
			return { __type = "list", elements = patches }
		end),

		barh = fn("barh", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local y_arr, cats = resolve_coord_or_categories(a[2] or kw.y)
			if cats then st.yticks = y_arr; st.yticklabels = cats end
			if not is_none(kw.tick_label) then
				st.yticks = y_arr
				st.yticklabels = to_string_array(kw.tick_label)
			end
			local w_arr = to_num_array(a[3] or kw.width)
			local h_in = a[4] or kw.height or 0.8
			local h_val = (type(h_in) == "table") and to_num_array(h_in) or (MathEngine.unwrap_number(h_in) or 0.8)
			local l_in = a[5] or kw.left or 0
			local l_val = (type(l_in) == "table") and to_num_array(l_in) or (MathEngine.unwrap_number(l_in) or 0)
			local style = extract_common_style(kw, nil, st, true)

			local bar_series = {
				kind = "barh",
				x = y_arr,
				height = w_arr,
				width = h_val,
				bottom = l_val,
				align = (not is_none(kw.align)) and RUtils.to_str(kw.align, false) or "center",
				color = style.color,
				edgecolor = style.edgecolor,
				linewidth = style.linewidth,
				alpha = style.alpha,
				label = style.label,
				bar_colors = {},
				bar_alphas = {},
				bar_edgecolors = {},
				bar_linewidths = {}
			}
			table.insert(st.series, bar_series)

			local patches = {}
			for idx = 1, min(#y_arr, #w_arr) do
				patches[idx] = make_bar_patch(bar_series, idx)
			end
			return { __type = "list", elements = patches }
		end),

		broken_barh = fn("broken_barh", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local raw_xr = a[2]
			local xranges = {}
			if type(raw_xr) == "table" and raw_xr.elements then
				for _, item in ipairs(raw_xr.elements) do
					local pair = to_num_array(item)
					if #pair >= 2 then xranges[#xranges + 1] = { pair[1], pair[2] } end
				end
			end
			local yrange = to_num_array(a[3])
			local fc_in = kw.facecolors or kw.facecolor or kw.color or kw.c
			local fcols = nil
			if type(fc_in) == "table" then
				fcols = to_string_array(fc_in)
			elseif not is_none(fc_in) then
				fcols = RUtils.to_str(fc_in, false)
			end
			local style = extract_common_style(kw, nil, st, true)
			table.insert(st.series, {
				kind = "broken_barh",
				xranges = xranges,
				yrange = yrange,
				facecolors = fcols,
				color = style.color,
				edgecolor = style.edgecolor,
				linewidth = style.linewidth,
				alpha = style.alpha,
				label = style.label
			})
			return self
		end),

		-- (8) 直方圖 (ax.hist)
		hist = fn("hist", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local data = to_num_array(a[2] or kw.x)
			local bins_in = a[3] or kw.bins or 10
			local is_density = (kw.density == true)
			local is_cum = (kw.cumulative == true)
			local histtype = (not is_none(kw.histtype)) and RUtils.to_str(kw.histtype, false) or "bar"
			local rwidth = (not is_none(kw.rwidth)) and MathEngine.unwrap_number(kw.rwidth) or 0.92

			local clean = {}
			local dMin, dMax = math.huge, -math.huge
			for _, v in ipairs(data) do
				if v and v == v and abs(v) < 1e12 then
					clean[#clean + 1] = v
					if v < dMin then dMin = v end
					if v > dMax then dMax = v end
				end
			end
			if not is_none(kw.range) then
				local r_arr = to_num_array(kw.range)
				if #r_arr >= 2 then dMin, dMax = r_arr[1], r_arr[2] end
			end
			if dMin == math.huge then dMin, dMax = 0, 1 end
			if dMin == dMax then dMin, dMax = dMin - 0.5, dMax + 0.5 end

			local edges = {}
			if type(bins_in) == "table" and bins_in.elements then
				edges = to_num_array(bins_in)
			else
				local n_bins = max(1, floor(MathEngine.unwrap_number(bins_in) or 10))
				local step = (dMax - dMin) / n_bins
				for b = 0, n_bins do edges[b + 1] = dMin + b * step end
			end

			local n_bins = max(1, #edges - 1)
			local counts = {}
			for b = 1, n_bins do counts[b] = 0 end
			for _, v in ipairs(clean) do
				if v >= edges[1] and v <= edges[#edges] then
					for b = 1, n_bins do
						if (b < n_bins and v >= edges[b] and v < edges[b + 1]) or (b == n_bins and v >= edges[b] and v <= edges[b + 1]) then
							counts[b] = counts[b] + 1
							break
						end
					end
				end
			end

			if is_density and #clean > 0 then
				local tot = #clean
				for b = 1, n_bins do
					local bw = max(1e-12, edges[b + 1] - edges[b])
					counts[b] = counts[b] / (tot * bw)
				end
			end
			if is_cum then
				local acc = 0
				for b = 1, n_bins do acc = acc + counts[b]; counts[b] = acc end
			end

			local centers, widths = {}, {}
			for b = 1, n_bins do
				centers[b] = 0.5 * (edges[b] + edges[b + 1])
				widths[b] = (edges[b + 1] - edges[b]) * rwidth
			end

			local style = extract_common_style(kw, nil, st, true)
			if histtype == "step" then
				table.insert(st.series, {
					kind = "line",
					x = centers,
					y = counts,
					color = style.color,
					linewidth = style.linewidth or 2.0,
					interpolate = "step-mid",
					label = style.label
				})
			else
				table.insert(st.series, {
					kind = "hist",
					x = centers,
					height = counts,
					width = widths,
					bottom = 0,
					align = "center",
					color = style.color,
					edgecolor = style.edgecolor or "#2b2b2b",
					linewidth = style.linewidth or 0.8,
					alpha = style.alpha or 0.85,
					label = style.label
				})
			end

			return {
				__type = "tuple",
				elements = {
					{ __type = "list", elements = counts },
					{ __type = "list", elements = edges },
					B.None
				}
			}
		end),

		-- (9) 圓餅圖與甜甜圈圖 (ax.pie)
		pie = fn("pie", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			st.projection = "pie"
			local x_arr = to_num_array(a[2] or kw.x)
			local labels = (not is_none(kw.labels)) and to_string_array(kw.labels) or nil
			local explode = (not is_none(kw.explode)) and to_num_array(kw.explode) or nil
			local colors = (not is_none(kw.colors)) and to_string_array(kw.colors) or nil
			local wp = py_dict_to_lua(kw.wedgeprops)

			table.insert(st.series, {
				kind = "pie",
				x = x_arr,
				labels = labels,
				explode = explode,
				colors = colors,
				autopct = (not is_none(kw.autopct)) and RUtils.to_str(kw.autopct, false) or nil,
				pctdistance = (not is_none(kw.pctdistance)) and MathEngine.unwrap_number(kw.pctdistance) or 0.6,
				labeldistance = (not is_none(kw.labeldistance)) and MathEngine.unwrap_number(kw.labeldistance) or 1.14,
				startangle = (not is_none(kw.startangle)) and MathEngine.unwrap_number(kw.startangle) or 0,
				counterclock = (kw.counterclock ~= false),
				wedge_width = wp.width and tonumber(wp.width) or nil,
				edgecolor = wp.edgecolor or (not is_none(kw.edgecolor) and RUtils.to_str(kw.edgecolor, false)) or "#ffffff",
				linewidth = wp.linewidth and tonumber(wp.linewidth) or 1.2
			})
			return { __type = "tuple", elements = { { __type = "list", elements = {} }, { __type = "list", elements = {} } } }
		end),

		-- (10) 箱形圖與小提琴圖 (ax.boxplot / ax.violinplot)
		boxplot = fn("boxplot", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local raw_x = a[2] or kw.x
			local datasets = {}
			if type(raw_x) == "table" and raw_x.elements and #raw_x.elements > 0 and type(raw_x.elements[1]) == "table" and raw_x.elements[1].elements then
				for _, sub in ipairs(raw_x.elements) do
					datasets[#datasets + 1] = to_num_array(sub)
				end
			else
				local grid = MathEngine.to2DGrid(raw_x)
				if #grid > 1 and type(grid[1]) == "table" and #grid[1] > 1 then
					datasets = grid
				else
					datasets = { to_num_array(raw_x) }
				end
			end

			local vert = (kw.vert ~= false)
			local positions = (not is_none(kw.positions)) and to_num_array(kw.positions) or {}
			if #positions == 0 then
				for k = 1, #datasets do positions[k] = k end
			end
			local lbl_in = kw.tick_labels or kw.labels
			if not is_none(lbl_in) then
				if vert then
					st.xticks = positions; st.xticklabels = to_string_array(lbl_in)
				else
					st.yticks = positions; st.yticklabels = to_string_array(lbl_in)
				end
			end

			local style = extract_common_style(kw, nil, st, true)
			table.insert(st.series, {
				kind = "boxplot",
				datasets = datasets,
				positions = positions,
				vert = vert,
				widths = (not is_none(kw.widths)) and MathEngine.unwrap_number(kw.widths) or 0.5,
				whis = (not is_none(kw.whis)) and MathEngine.unwrap_number(kw.whis) or 1.5,
				patch_artist = (kw.patch_artist == true),
				showmeans = (kw.showmeans == true),
				showfliers = (kw.showfliers ~= false),
				color = style.color,
				alpha = style.alpha
			})
			return { __type = "dict", pairs = {} }
		end),

		violinplot = fn("violinplot", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local raw_d = a[2] or kw.dataset
			local datasets = {}
			if type(raw_d) == "table" and raw_d.elements and #raw_d.elements > 0 and type(raw_d.elements[1]) == "table" and raw_d.elements[1].elements then
				for _, sub in ipairs(raw_d.elements) do
					datasets[#datasets + 1] = to_num_array(sub)
				end
			else
				local grid = MathEngine.to2DGrid(raw_d)
				if #grid > 1 and type(grid[1]) == "table" and #grid[1] > 1 then
					datasets = grid
				else
					datasets = { to_num_array(raw_d) }
				end
			end

			local vert = (kw.vert ~= false)
			local positions = (not is_none(a[3] or kw.positions)) and to_num_array(a[3] or kw.positions) or {}
			if #positions == 0 then
				for k = 1, #datasets do positions[k] = k end
			end
			local style = extract_common_style(kw, nil, st, true)
			table.insert(st.series, {
				kind = "violinplot",
				datasets = datasets,
				positions = positions,
				vert = vert,
				widths = (not is_none(kw.widths)) and MathEngine.unwrap_number(kw.widths) or 0.55,
				showmeans = (kw.showmeans == true),
				showmedians = (kw.showmedians == true),
				showextrema = (kw.showextrema ~= false),
				color = style.color,
				alpha = style.alpha or 0.50,
				label = style.label
			})
			return { __type = "dict", pairs = {} }
		end),

		-- (11) 誤差條圖 (ax.errorbar)
		errorbar = fn("errorbar", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local x_arr, cats = resolve_coord_or_categories(a[2] or kw.x)
			if cats then st.xticks = x_arr; st.xticklabels = cats end
			local y_arr = to_num_array(a[3] or kw.y)
			local yerr_in = a[4] or kw.yerr
			local xerr_in = a[5] or kw.xerr
			local fmt_s = (not is_none(kw.fmt)) and RUtils.to_str(kw.fmt, false) or nil
			local style = extract_common_style(kw, fmt_s, st, false)
			local ylo, yhi = parse_error_bars(yerr_in, #x_arr)
			local xlo, xhi = parse_error_bars(xerr_in, #x_arr)

			table.insert(st.series, {
				kind = "errorbar",
				x = x_arr,
				y = y_arr,
				yerr_low = ylo, yerr_high = yhi,
				xerr_low = xlo, xerr_high = xhi,
				color = style.color,
				linestyle = (fmt_s == "none" or fmt_s == "o") and "none" or style.linestyle,
				linewidth = style.linewidth,
				marker = style.marker or "o",
				markersize = style.markersize,
				ecolor = (not is_none(kw.ecolor)) and RUtils.to_str(kw.ecolor, false) or nil,
				elinewidth = (not is_none(kw.elinewidth)) and MathEngine.unwrap_number(kw.elinewidth) or nil,
				capsize = (not is_none(kw.capsize)) and MathEngine.unwrap_number(kw.capsize) or 3.5,
				alpha = style.alpha,
				label = style.label
			})
			return self
		end),

		-- (12) 熱圖與矩陣視覺化 (ax.imshow / ax.pcolormesh / ax.matshow)
		imshow = fn("imshow", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local grid = MathEngine.to2DGrid(a[2] or kw.X)
			local ext = (not is_none(kw.extent)) and to_num_array(kw.extent) or nil
			local cmap = resolve_cmap_arg(kw.cmap, st.colormap or "viridis")
			table.insert(st.series, {
				kind = "imshow",
				grid = grid,
				cmap = cmap,
				vmin = (not is_none(kw.vmin)) and MathEngine.unwrap_number(kw.vmin) or nil,
				vmax = (not is_none(kw.vmax)) and MathEngine.unwrap_number(kw.vmax) or nil,
				extent = ext,
				origin = (not is_none(kw.origin)) and RUtils.to_str(kw.origin, false) or "upper",
				alpha = (not is_none(kw.alpha)) and MathEngine.unwrap_number(kw.alpha) or nil
			})
			return self
		end),

		pcolormesh = fn("pcolormesh", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local grid, ext
			if #a >= 4 then
				local x_arr = to_num_array(a[2])
				local y_arr = to_num_array(a[3])
				grid = MathEngine.to2DGrid(a[4])
				if #x_arr >= 2 and #y_arr >= 2 then
					ext = { x_arr[1], x_arr[#x_arr], y_arr[1], y_arr[#y_arr] }
				end
			else
				grid = MathEngine.to2DGrid(a[2])
			end
			table.insert(st.series, {
				kind = "imshow",
				grid = grid,
				cmap = resolve_cmap_arg(kw.cmap, st.colormap or "viridis"),
				vmin = (not is_none(kw.vmin)) and MathEngine.unwrap_number(kw.vmin) or nil,
				vmax = (not is_none(kw.vmax)) and MathEngine.unwrap_number(kw.vmax) or nil,
				extent = ext,
				origin = "lower"
			})
			return self
		end),

		-- (13) 3D 曲面與網格 (ax.plot_surface / ax.plot_wireframe / ax.view_init)
		plot_surface = fn("plot_surface", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			st.projection = "3d"
			local grid, nu, nv = MathEngine.prepareSurfaceGrid(a[2], a[3], a[4], kw.rstride, kw.cstride, 32)
			table.insert(st.series, {
				kind = "surface3d",
				grid = grid, nu = nu, nv = nv,
				cmap = resolve_cmap_arg(kw.cmap, nil),
				color = (not is_none(kw.color or kw.c)) and RUtils.to_str(kw.color or kw.c, false) or nil,
				edgecolor = (not is_none(kw.edgecolor or kw.ec)) and RUtils.to_str(kw.edgecolor or kw.ec, false) or nil,
				linewidth = (not is_none(kw.linewidth or kw.lw)) and MathEngine.unwrap_number(kw.linewidth or kw.lw) or nil,
				alpha = (not is_none(kw.alpha)) and MathEngine.unwrap_number(kw.alpha) or 0.92,
				shade = (kw.shade ~= false),
				label = (not is_none(kw.label)) and RUtils.to_str(kw.label, false) or nil
			})
			return self
		end),

		plot_wireframe = fn("plot_wireframe", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			st.projection = "3d"
			local grid, nu, nv = MathEngine.prepareSurfaceGrid(a[2], a[3], a[4], kw.rstride, kw.cstride, 28)
			local style = extract_common_style(kw, nil, st, false)
			table.insert(st.series, {
				kind = "wireframe3d",
				grid = grid, nu = nu, nv = nv,
				color = style.color,
				linewidth = style.linewidth or 1.0,
				alpha = style.alpha or 1.0,
				label = style.label
			})
			return self
		end),

		view_init = fn("view_init", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local elev = a[2] or kw.elev
			local azim = a[3] or kw.azim
			local roll = a[4] or kw.roll
			if not is_none(elev) then st.elev = MathEngine.unwrap_number(elev) end
			if not is_none(azim) then st.azim = MathEngine.unwrap_number(azim) end
			if not is_none(roll) then st.roll = MathEngine.unwrap_number(roll) end
			return B.None
		end),

		-- (14) 複變函數定義域著色圖 (ax.complex_plot)
		complex_plot = fn("complex_plot", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			st.projection = "complex"
			local xlim = (not is_none(kw.xlim)) and to_num_array(kw.xlim) or { -5, 5 }
			local ylim = (not is_none(kw.ylim)) and to_num_array(kw.ylim) or { -5, 5 }
			local res = (not is_none(kw.resolution or kw.sampling)) and MathEngine.unwrap_number(kw.resolution or kw.sampling) or 60
			local cdata = MathEngine.buildComplexDomainPixels(a[2], xlim[1], xlim[2], ylim[1], ylim[2], res)
			cdata.axes = (kw.axes ~= false)
			cdata.inner_axes = (kw.inner_axes ~= false and kw.innerAxes ~= false)
			cdata.inner_ticks = (kw.inner_ticks == true or kw.innerTicks == true)
			cdata.inner_tick_labels = (kw.inner_tick_labels == true or kw.innerTickLabels == true)
			cdata.outer_ticks = (kw.outer_ticks ~= false and kw.outerTicks ~= false)
			cdata.tick_labels = (kw.tick_labels ~= false and kw.tickLabels ~= false)
			cdata.axis_titles = (kw.axis_titles ~= false and kw.axisTitles ~= false)
			cdata.grid = (kw.grid == true)
			cdata.grid_style = kw.grid_style or kw.gridStyle
			cdata.imag_suffix = kw.imag_suffix or kw.imagSuffix or ""
			st.complex_data = cdata
			return self
		end),

		-- (15) 參考線、文字與註解 (axhline, axvline, text, annotate)
		axhline = fn("axhline", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local style = extract_common_style(kw, nil, st, false)
			table.insert(st.series, {
				kind = "hline",
				pos = MathEngine.unwrap_number(a[2] or kw.y or 0),
				min_frac = (not is_none(a[3] or kw.xmin)) and MathEngine.unwrap_number(a[3] or kw.xmin) or 0,
				max_frac = (not is_none(a[4] or kw.xmax)) and MathEngine.unwrap_number(a[4] or kw.xmax) or 1,
				color = style.color,
				linestyle = style.linestyle,
				linewidth = style.linewidth or 1.4,
				alpha = style.alpha,
				label = style.label
			})
			return self
		end),

		axvline = fn("axvline", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local style = extract_common_style(kw, nil, st, false)
			table.insert(st.series, {
				kind = "vline",
				pos = MathEngine.unwrap_number(a[2] or kw.x or 0),
				min_frac = (not is_none(a[3] or kw.ymin)) and MathEngine.unwrap_number(a[3] or kw.ymin) or 0,
				max_frac = (not is_none(a[4] or kw.ymax)) and MathEngine.unwrap_number(a[4] or kw.ymax) or 1,
				color = style.color,
				linestyle = style.linestyle,
				linewidth = style.linewidth or 1.4,
				alpha = style.alpha,
				label = style.label
			})
			return self
		end),

		text = fn("text", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			table.insert(st.texts, {
				x = MathEngine.unwrap_number(a[2] or kw.x or 0),
				y = MathEngine.unwrap_number(a[3] or kw.y or 0),
				text = RUtils.to_str(a[4] or kw.s or "", false),
				fontsize = (not is_none(kw.fontsize or kw.size)) and MathEngine.unwrap_number(kw.fontsize or kw.size) or 11,
				color = (not is_none(kw.color or kw.c)) and RUtils.to_str(kw.color or kw.c, false) or "#222222",
				ha = (not is_none(kw.ha or kw.horizontalalignment)) and RUtils.to_str(kw.ha or kw.horizontalalignment, false) or "left"
			})
			return self
		end),

		annotate = fn("annotate", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			local txt = RUtils.to_str(a[2] or kw.text or "", false)
			local xy = to_num_array(a[3] or kw.xy)
			local xytext = (not is_none(a[4] or kw.xytext)) and to_num_array(a[4] or kw.xytext) or xy
			local has_arrow = not is_none(kw.arrowprops) and (xytext ~= xy)
			table.insert(st.texts, {
				x = xytext[1] or 0,
				y = xytext[2] or 0,
				text = txt,
				arrow_to = has_arrow and xy or nil,
				fontsize = (not is_none(kw.fontsize or kw.size)) and MathEngine.unwrap_number(kw.fontsize or kw.size) or 11,
				color = (not is_none(kw.color or kw.c)) and RUtils.to_str(kw.color or kw.c, false) or "#222222",
				ha = (not is_none(kw.ha)) and RUtils.to_str(kw.ha, false) or "left"
			})
			return self
		end),

		-- (16) 座標軸與標題設定器 (set_title, set_xlabel, set_xticks, set_xticklabels, legend, grid 等)
		set_title = fn("set_title", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			st.title = RUtils.to_str(a[2] or kw.label or "", false)
			if not is_none(kw.fontsize or kw.size) then
				st.title_fontsize = MathEngine.unwrap_number(kw.fontsize or kw.size)
			end
			if not is_none(kw.color or kw.c) then
				st.title_color = RUtils.to_str(kw.color or kw.c, false)
			end
			if not is_none(kw.y) then
				st.title_y = MathEngine.unwrap_number(kw.y)
			end
			return B.None
		end),

		set_xlabel = fn("set_xlabel", function(a)
			a[1].__dict._state.xlabel = RUtils.to_str(a[2] or "", false)
			return B.None
		end),

		set_ylabel = fn("set_ylabel", function(a)
			a[1].__dict._state.ylabel = RUtils.to_str(a[2] or "", false)
			return B.None
		end),

		set_zlabel = fn("set_zlabel", function(a)
			a[1].__dict._state.zlabel = RUtils.to_str(a[2] or "", false)
			return B.None
		end),

		set_xlim = fn("set_xlim", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			if type(a[2]) == "table" and a[2].elements then
				st.xlim = to_num_array(a[2])
			else
				local lo = a[2] or kw.left or kw.xmin
				local hi = a[3] or kw.right or kw.xmax
				st.xlim = { not is_none(lo) and MathEngine.unwrap_number(lo) or nil, not is_none(hi) and MathEngine.unwrap_number(hi) or nil }
			end
			return B.None
		end),

		set_ylim = fn("set_ylim", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			if type(a[2]) == "table" and a[2].elements then
				st.ylim = to_num_array(a[2])
			else
				local lo = a[2] or kw.bottom or kw.ymin
				local hi = a[3] or kw.top or kw.ymax
				st.ylim = { not is_none(lo) and MathEngine.unwrap_number(lo) or nil, not is_none(hi) and MathEngine.unwrap_number(hi) or nil }
			end
			return B.None
		end),

		set_zlim = fn("set_zlim", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			if type(a[2]) == "table" and a[2].elements then
				st.zlim = to_num_array(a[2])
			else
				local lo = a[2] or kw.bottom or kw.zmin
				local hi = a[3] or kw.top or kw.zmax
				st.zlim = { not is_none(lo) and MathEngine.unwrap_number(lo) or nil, not is_none(hi) and MathEngine.unwrap_number(hi) or nil }
			end
			return B.None
		end),

		set_xticks = fn("set_xticks", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			st.xticks = to_num_array(a[2])
			if not is_none(a[3] or kw.labels) then
				st.xticklabels = to_string_array(a[3] or kw.labels)
			end
			return B.None
		end),

		set_yticks = fn("set_yticks", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			st.yticks = to_num_array(a[2])
			if not is_none(a[3] or kw.labels) then
				st.yticklabels = to_string_array(a[3] or kw.labels)
			end
			return B.None
		end),

		set_zticks = fn("set_zticks", function(a)
			a[1].__dict._state.zticks = to_num_array(a[2])
			return B.None
		end),

		set_xticklabels = fn("set_xticklabels", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			st.xticklabels = to_string_array(a[2])
			if not is_none(kw.fontsize or kw.size) then
				st.xtick_fontsize = MathEngine.unwrap_number(kw.fontsize or kw.size)
			end
			return B.None
		end),

		set_yticklabels = fn("set_yticklabels", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			st.yticklabels = to_string_array(a[2])
			if not is_none(kw.fontsize or kw.size) then
				st.ytick_fontsize = MathEngine.unwrap_number(kw.fontsize or kw.size)
			end
			return B.None
		end),

		set_aspect = fn("set_aspect", function(a)
			local st = a[1].__dict._state
			local asp = unwrap_scalar(a[2])
			st.aspect = asp
			if asp == "equal" or asp == 1 then st.equal_scale = true end
			return B.None
		end),

		set_box_aspect = fn("set_box_aspect", function(a)
			a[1].__dict._state.box_aspect = a[2]
			return B.None
		end),

		grid = fn("grid", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			local b_val = a[2]
			if is_none(b_val) then b_val = kw.visible end
			if is_none(b_val) then st.grid = true
			else st.grid = RUtils.to_bool(b_val) end
			local axis = (not is_none(kw.axis)) and RUtils.to_str(kw.axis, false) or "both"
			if axis == "x" then st.x_grid = st.grid; st.y_grid = false
			elseif axis == "y" then st.y_grid = st.grid; st.x_grid = false
			else st.x_grid = st.grid; st.y_grid = st.grid end
			return B.None
		end),

		legend = fn("legend", function(a, kw)
			local st = a[1].__dict._state
			kw = kw or {}
			st.show_legend = true
			if not is_none(kw.loc) then st.legend_loc = RUtils.to_str(kw.loc, false) end
			if not is_none(kw.title) then st.legend_title = RUtils.to_str(kw.title, false) end
			if not is_none(kw.fontsize) then st.legend_fontsize = MathEngine.unwrap_number(kw.fontsize) end
			if not is_none(a[2]) and type(a[2]) == "table" then
				local lbls = to_string_array(a[2])
				for i, s in ipairs(st.series) do
					if lbls[i] then s.label = lbls[i] end
				end
			end
			return B.None
		end),

		axis = fn("axis", function(a)
			local st = a[1].__dict._state
			local arg = unwrap_scalar(a[2])
			if arg == "off" or arg == false then st.show_axes = false
			elseif arg == "on" or arg == true then st.show_axes = true
			elseif arg == "equal" then st.equal_scale = true
			elseif type(a[2]) == "table" then
				local bounds = to_num_array(a[2])
				if #bounds >= 4 then
					st.xlim = { bounds[1], bounds[2] }
					st.ylim = { bounds[3], bounds[4] }
				end
			end
			return B.None
		end),

		set_rlim = fn("set_rlim", function(a)
			local st = a[1].__dict._state
			local rmax = MathEngine.unwrap_number(a[3] or a[2])
			if rmax then
				st.xlim = { -rmax, rmax }
				st.ylim = { -rmax, rmax }
			end
			return B.None
		end),

		set_rticks = fn("set_rticks", function(a)
			a[1].__dict._state.yticks = to_num_array(a[2])
			return B.None
		end),

		-- 支援 ax.set(title=..., xlabel=..., ylabel=..., xlim=..., ylim=..., ...) 批次設定與進階自訂屬性
		set = fn("set", function(a, kw)
			local self = a[1]
			local st = self.__dict._state
			kw = kw or {}
			if kw.title then st.title = RUtils.to_str(kw.title, false) end
			if kw.xlabel then st.xlabel = RUtils.to_str(kw.xlabel, false) end
			if kw.ylabel then st.ylabel = RUtils.to_str(kw.ylabel, false) end
			if kw.zlabel then st.zlabel = RUtils.to_str(kw.zlabel, false) end
			if kw.xlim then st.xlim = to_num_array(kw.xlim) end
			if kw.ylim then st.ylim = to_num_array(kw.ylim) end
			if kw.zlim then st.zlim = to_num_array(kw.zlim) end
			if kw.xticks then st.xticks = to_num_array(kw.xticks) end
			if kw.yticks then st.yticks = to_num_array(kw.yticks) end
			if kw.xticklabels then st.xticklabels = to_string_array(kw.xticklabels) end
			if kw.yticklabels then st.yticklabels = to_string_array(kw.yticklabels) end
			if kw.aspect == "equal" or kw.equal_scale == true then st.equal_scale = true end
			if kw.strict_ticks ~= nil then st.strict_ticks = RUtils.to_bool(kw.strict_ticks) end
			if kw.x_unit then st.x_unit = MathEngine.unwrap_number(kw.x_unit) end
			if kw.x_unit_label then st.x_unit_label = RUtils.to_str(kw.x_unit_label, false) end
			if kw.y_unit then st.y_unit = MathEngine.unwrap_number(kw.y_unit) end
			if kw.y_unit_label then st.y_unit_label = RUtils.to_str(kw.y_unit_label, false) end
			if kw.tick_fraction ~= nil then st.tick_fraction = RUtils.to_bool(kw.tick_fraction) end
			if kw.polar_shape then st.polar_shape = RUtils.to_str(kw.polar_shape, false) end
			if kw.origin_axes ~= nil then st.origin_axes = RUtils.to_bool(kw.origin_axes) end
			return B.None
		end),

		get_xlim = fn("get_xlim", function(a)
			local xl = a[1].__dict._state.xlim or { 0.0, 1.0 }
			return { __type = "tuple", elements = { xl[1] or 0.0, xl[2] or 1.0 } }
		end),

		get_ylim = fn("get_ylim", function(a)
			local yl = a[1].__dict._state.ylim or { 0.0, 1.0 }
			return { __type = "tuple", elements = { yl[1] or 0.0, yl[2] or 1.0 } }
		end),

		get_zlim = fn("get_zlim", function(a)
			local zl = a[1].__dict._state.zlim or { 0.0, 1.0 }
			return { __type = "tuple", elements = { zl[1] or 0.0, zl[2] or 1.0 } }
		end),

		cla = fn("cla", function(a)
			local st = a[1].__dict._state
			st.series = {}
			st.texts = {}
			st.title = nil
			st._line_color_idx = 0
			st._patch_color_idx = 0
			return B.None
		end)
	}

	axes_dict.plot3D = axes_dict.plot
	axes_dict.scatter3D = axes_dict.scatter
	axes_dict.matshow = axes_dict.imshow
	axes_dict.clear = axes_dict.cla
	-- 綁定 3D 專屬方法別名 (set_xlim3d, set_ylim3d, set_zlim3d, get_zlim3d 等)
	axes_dict.set_xlim3d = axes_dict.set_xlim
	axes_dict.set_ylim3d = axes_dict.set_ylim
	axes_dict.set_zlim3d = axes_dict.set_zlim
	axes_dict.get_xlim3d = axes_dict.get_xlim
	axes_dict.get_ylim3d = axes_dict.get_ylim
	axes_dict.get_zlim3d = axes_dict.get_zlim
	axes_dict.plot_trisurf = axes_dict.plot_surface
	axes_dict.contour3D = axes_dict.plot_wireframe
	axes_dict.contourf3D = axes_dict.plot_surface

	AxesClass = PyClass.make_class("Axes", { B.object }, axes_dict)
	AxesClass.__dict["__module__"] = "matplotlib.axes"
	return AxesClass
end

local Axes3DClass = nil
local make_axes_instance

local function get_axes3d_class()
	if Axes3DClass then return Axes3DClass end
	get_axes_class()
	local a3d_dict = {
		-- 支援舊式語法 ax = Axes3D(fig) 直接將 3D 座標軸掛載至 Figure
		__new__ = {
			__type = "staticmethod",
			__func = fn("__new__", function(a, kw)
				local fig_arg = a[2]
				if type(fig_arg) == "table" and fig_arg.__dict and fig_arg.__dict._state then
					return make_axes_instance(fig_arg, 1, 1, {
						projection = "3d",
						elev = kw and kw.elev,
						azim = kw and kw.azim
					})
				end
				return PyClass.make_instance(Axes3DClass)
			end)
		},
		__init__ = fn("__init__", function() return nil end)
	}
	Axes3DClass = PyClass.make_class("Axes3D", { AxesClass }, a3d_dict)
	Axes3DClass.__dict["__module__"] = "mpl_toolkits.mplot3d.axes3d"
	return Axes3DClass
end

make_axes_instance = function(fig_inst, row, col, opts)
	opts = opts or {}
	local proj = opts.projection or (opts.polar == true and "polar") or "rectilinear"
	local target_cls = (proj == "3d") and get_axes3d_class() or get_axes_class()
	local inst = PyClass.make_instance(target_cls)
	inst.__dict._state = {
		row = row or 1,
		col = col or 1,
		projection = proj,
		elev = opts.elev and MathEngine.unwrap_number(opts.elev) or nil,
		azim = opts.azim and MathEngine.unwrap_number(opts.azim) or nil,
		polar_shape = opts.polar_shape,
		equal_scale = opts.equal_scale,
		strict_ticks = opts.strict_ticks,
		x_unit = opts.x_unit,
		x_unit_label = opts.x_unit_label,
		y_unit = opts.y_unit,
		y_unit_label = opts.y_unit_label,
		tick_fraction = opts.tick_fraction,
		series = {},
		texts = {},
		palette = fig_inst.__dict._state.palette,
		_line_color_idx = 0,
		_patch_color_idx = 0
	}
	inst.__dict.xaxis = make_axis_artist(inst.__dict._state, "x")
	inst.__dict.yaxis = make_axis_artist(inst.__dict._state, "y")
	inst.__dict.zaxis = make_axis_artist(inst.__dict._state, "z")
	table.insert(fig_inst.__dict._state.axes_list, inst.__dict._state)
	table.insert(fig_inst.__dict._axes_objs, inst)
	fig_inst.__dict._current_ax = inst
	return inst
end

local function get_figure_class()
	if FigureClass then return FigureClass end

	local fig_dict = {
		add_subplot = fn("add_subplot", function(a, kw)
			local self = a[1]
			local fst = self.__dict._state
			kw = kw or {}
			local nr, nc, idx = 1, 1, 1
			if #a == 2 and type(a[2]) == "number" and a[2] >= 100 then
				local code = floor(a[2])
				nr = floor(code / 100)
				nc = floor((code % 100) / 10)
				idx = code % 10
			elseif #a >= 4 then
				nr = floor(MathEngine.unwrap_number(a[2]) or 1)
				nc = floor(MathEngine.unwrap_number(a[3]) or 1)
				idx = floor(MathEngine.unwrap_number(a[4]) or 1)
			end
			if nr > fst.nrows then fst.nrows = nr end
			if nc > fst.ncols then fst.ncols = nc end
			local r = floor((idx - 1) / max(1, nc)) + 1
			local c = ((idx - 1) % max(1, nc)) + 1
			return make_axes_instance(self, r, c, {
				projection = (not is_none(kw.projection)) and RUtils.to_str(kw.projection, false) or nil,
				polar = (kw.polar == true),
				polar_shape = (not is_none(kw.polar_shape)) and RUtils.to_str(kw.polar_shape, false) or nil
			})
		end),

		add_axes = fn("add_axes", function(a, kw)
			local self = a[1]
			kw = kw or {}
			return make_axes_instance(self, 1, 1, {
				projection = (not is_none(kw.projection)) and RUtils.to_str(kw.projection, false) or nil,
				polar = (kw.polar == true)
			})
		end),

		suptitle = fn("suptitle", function(a)
			a[1].__dict._state.suptitle = RUtils.to_str(a[2] or "", false)
			return B.None
		end),

		colorbar = fn("colorbar", function(a, kw)
			local self = a[1]
			kw = kw or {}
			local target_ax = kw.ax or self.__dict._current_ax
			if target_ax and target_ax.__dict and target_ax.__dict._state then
				target_ax.__dict._state.colorbar = true
			end
			return B.None
		end),

		tight_layout = fn("tight_layout", function() return B.None end),

		clear = fn("clear", function(a)
			local self = a[1]
			self.__dict._state.axes_list = {}
			self.__dict._axes_objs = {}
			self.__dict._current_ax = nil
			return B.None
		end),

		to_svg = fn("to_svg", function(a)
			return SVGRenderer.renderFigure(a[1].__dict._state)
		end),

		show = fn("show", function(a)
			local self = a[1]
			local svg_out = SVGRenderer.renderFigure(self.__dict._state)
			self.__dict._shown = true
			if Runtime.builtins and Runtime.builtins.print then
				Runtime.builtins.print({ svg_out }, {})
			else
				table.insert(Runtime.stdout, svg_out .. "\n")
			end
			return svg_out
		end),

		savefig = fn("savefig", function(a)
			local self = a[1]
			local target = a[2]
			local svg_out = SVGRenderer.renderFigure(self.__dict._state)
			if type(target) == "table" and target.__type == "instance" then
				local ok, write_m = pcall(PyClass.get_attr, target, "write")
				if ok and write_m then
					require("Module:沙盒/a2569875/PythonScript/Evaluator").invoke(write_m, { svg_out }, {})
				end
			end
			return svg_out
		end)
	}
	fig_dict.clf = fig_dict.clear

	FigureClass = PyClass.make_class("Figure", { B.object }, fig_dict)
	FigureClass.__dict["__module__"] = "matplotlib.figure"
	return FigureClass
end

-- ==========================================
-- 3. Pyplot 多畫布佇列 (Figure Queue) 與狀態機
-- ==========================================
local active_fig = nil
local figure_queue = {}

local function register_figure(fig, num_id)
	if num_id ~= nil then
		fig.__dict._num = num_id
	end
	for _, existing in ipairs(figure_queue) do
		if existing == fig then
			active_fig = fig
			return fig
		end
	end
	table.insert(figure_queue, fig)
	active_fig = fig
	return fig
end

local function find_figure_by_num(num_id)
	if num_id == nil then return nil end
	for _, fig in ipairs(figure_queue) do
		if fig.__dict._num == num_id then
			return fig
		end
	end
	return nil
end

local function make_figure_instance(kw, num_arg)
	kw = kw or {}
	local num_id = unwrap_scalar(num_arg or kw.num)
	local existing = find_figure_by_num(num_id)
	if existing then
		existing.__dict._shown = false
		active_fig = existing
		return existing
	end

	local w, h = 420, 300
	if not is_none(kw.figsize) then
		local fs = to_num_array(kw.figsize)
		if #fs >= 2 then
			w = max(160, min(900, floor(fs[1] * 72 + 0.5)))
			h = max(120, min(800, floor(fs[2] * 72 + 0.5)))
		end
	end
	if not is_none(kw.width) then w = MathEngine.unwrap_number(kw.width) end
	if not is_none(kw.height) then h = MathEngine.unwrap_number(kw.height) end

	local inst = PyClass.make_instance(get_figure_class())
	inst.__dict._state = {
		width = w,
		height = h,
		nrows = 1,
		ncols = 1,
		suptitle = nil,
		html_labels = (kw.html_labels ~= false),
		animate = kw.animate,
		palette = RendererUtils.DEFAULT_COLORS,
		axes_list = {}
	}
	inst.__dict._axes_objs = {}
	inst.__dict._current_ax = nil
	inst.__dict._shown = false
	return register_figure(inst, num_id or (#figure_queue + 1))
end

local function ensure_fig(kw)
	if not active_fig then
		active_fig = make_figure_instance(kw)
	end
	return active_fig
end

local function ensure_ax(kw)
	local fig = ensure_fig(kw)
	if not fig.__dict._current_ax then
		make_axes_instance(fig, 1, 1, kw)
	end
	return fig.__dict._current_ax
end

local function forward_to_ax(method_name)
	return fn(method_name, function(a, kw)
		local ax = ensure_ax(kw)
		local call_args = { ax }
		for i = 1, #(a or {}) do call_args[#call_args + 1] = a[i] end
		local m = get_axes_class().__dict[method_name]
		return m.__call(call_args, kw)
	end)
end

local mod_cache = {}

function Matplotlib.get_module(name)
	name = name or "matplotlib.pyplot"
	if mod_cache[name] then return mod_cache[name] end

	local Library = require("Module:沙盒/a2569875/PythonScript/Library")
	get_axes_class()
	get_axes3d_class()
	get_figure_class()

	-- 建構 mpl_toolkits.mplot3d 與 mpl_toolkits.axes_grid1 完整家族
	if not mod_cache["mpl_toolkits"] then
		local m3d_ns = Library.make_namespace("mpl_toolkits.mplot3d", {
			Axes3D = Axes3DClass,
			axes3d = Library.make_namespace("mpl_toolkits.mplot3d.axes3d", { Axes3D = Axes3DClass })
		})
		mod_cache["mpl_toolkits.mplot3d"] = m3d_ns
		mod_cache["mpl_toolkits.mplot3d.axes3d"] = m3d_ns.__dict.axes3d

		-- ImageGrid / AxesGrid 類別:建立多子圖網格並支援索引 grid[i]、len(grid) 與 for ax in grid
		local GridClass = PyClass.make_class("ImageGrid", { B.object }, {
			__init__ = fn("__init__", function(a, kw)
				local self = a[1]
				kw = kw or {}
				local fig = a[2] or kw.fig or ensure_fig()
				local nr_nc = to_num_array(a[4] or kw.nrows_ncols or { 1, 1 })
				local nr = max(1, floor(nr_nc[1] or 1))
				local nc = max(1, floor(nr_nc[2] or 1))
				local fst = fig.__dict._state
				if nr > fst.nrows then fst.nrows = nr end
				if nc > fst.ncols then fst.ncols = nc end

				local axes_all = {}
				for r = 1, nr do
					for c = 1, nc do
						local ax = make_axes_instance(fig, r, c, {})
						if kw.cbar_mode == "single" or kw.cbar_mode == "each" then
							ax.__dict._state.colorbar = true
						end
						axes_all[#axes_all + 1] = ax
					end
				end
				self.__dict.axes_all = { __type = "list", elements = axes_all }
				self.__dict.cbar_axes = { __type = "list", elements = axes_all }
				self.__dict._axes = axes_all
				return nil
			end),
			__getitem__ = fn("__getitem__", function(a)
				local self, idx = a[1], floor(MathEngine.unwrap_number(a[2]) or 0)
				local list = self.__dict._axes or {}
				if idx < 0 then idx = #list + idx end
				return list[idx + 1] or B.None
			end),
			__len__ = fn("__len__", function(a) return #(a[1].__dict._axes or {}) end),
			__iter__ = fn("__iter__", function(a) return B.iter({ a[1].__dict.axes_all }) end)
		})

		-- AxesDivider 類別:支援 make_axes_locatable(ax).append_axes("right", size="5%", pad=0.05)
		local DividerClass = PyClass.make_class("AxesDivider", { B.object }, {
			append_axes = fn("append_axes", function(a)
				local self = a[1]
				local pos = string.lower(RUtils.to_str(a[2] or "right", false))
				local parent_ax = self.__dict._ax
				if parent_ax and parent_ax.__dict and parent_ax.__dict._state then
					if pos == "right" or pos == "left" or pos == "top" or pos == "bottom" then
						parent_ax.__dict._state.colorbar = true
					end
				end
				return parent_ax
			end),
			new_horizontal = fn("new_horizontal", function(a) return a[1].__dict._ax end),
			new_vertical   = fn("new_vertical", function(a) return a[1].__dict._ax end)
		})

		local make_locatable_fn = fn("make_axes_locatable", function(a)
			local inst = PyClass.make_instance(DividerClass)
			inst.__dict._ax = a[1]
			return inst
		end)

		local ag1_ns = Library.make_namespace("mpl_toolkits.axes_grid1", {
			ImageGrid = GridClass,
			AxesGrid = GridClass,
			Grid = GridClass,
			make_axes_locatable = make_locatable_fn
		})
		mod_cache["mpl_toolkits.axes_grid1"] = ag1_ns
		mod_cache["mpl_toolkits.axes_grid"] = ag1_ns

		mod_cache["mpl_toolkits"] = Library.make_namespace("mpl_toolkits", {
			mplot3d = m3d_ns,
			axes_grid1 = ag1_ns,
			axes_grid = ag1_ns
		})
	end

	if string.match(name, "^mpl_toolkits") then
		return mod_cache[name] or mod_cache["mpl_toolkits"]
	end

	local cm_dict = {}
	for cname, _ in pairs(RendererUtils.COLORMAPS) do
		cm_dict[cname] = make_colormap_obj(cname)
	end
	cm_dict.get_cmap = fn("get_cmap", function(a)
		local n = (a and a[1] and RUtils.to_str(a[1], false)) or "viridis"
		return make_colormap_obj(n)
	end)
	local cm_ns = Library.make_namespace("matplotlib.cm", cm_dict)
	mod_cache["matplotlib.cm"] = cm_ns

	local LocatorClass = PyClass.make_class("Locator", { B.object }, {})
	local FormatterClass = PyClass.make_class("Formatter", { B.object }, {})
	local ticker_ns = Library.make_namespace("matplotlib.ticker", {
		MultipleLocator = fn("MultipleLocator", function(a, kw)
			local inst = PyClass.make_instance(LocatorClass)
			inst.__dict._kind = "MultipleLocator"
			inst.__dict.base = MathEngine.unwrap_number((a and a[1]) or (kw and kw.base) or 1)
			return inst
		end),
		FixedLocator = fn("FixedLocator", function(a)
			local inst = PyClass.make_instance(LocatorClass)
			inst.__dict._kind = "FixedLocator"
			inst.__dict.locs = to_num_array(a and a[1])
			return inst
		end),
		NullLocator = fn("NullLocator", function()
			local inst = PyClass.make_instance(LocatorClass)
			inst.__dict._kind = "NullLocator"
			return inst
		end),
		NullFormatter = fn("NullFormatter", function()
			local inst = PyClass.make_instance(FormatterClass)
			inst.__dict._kind = "NullFormatter"
			return inst
		end),
		FormatStrFormatter = fn("FormatStrFormatter", function(a)
			local inst = PyClass.make_instance(FormatterClass)
			inst.__dict._kind = "FormatStrFormatter"
			inst.__dict.fmt = RUtils.to_str(a and a[1] or "%g", false)
			return inst
		end)
	})
	mod_cache["matplotlib.ticker"] = ticker_ns

	local style_ns = Library.make_namespace("matplotlib.style", {
		available = { __type = "list", elements = { "default", "classic", "seaborn-v0_8", "ggplot" } },
		use = fn("use", function() return B.None end)
	})
	mod_cache["matplotlib.style"] = style_ns

	if name == "matplotlib.cm" or name == "matplotlib.ticker" or name == "matplotlib.style" then
		return mod_cache[name]
	end

	local plt_dict = {
		Figure = FigureClass,
		Axes = AxesClass,
		style = style_ns,
		cm = cm_ns,
		ticker = ticker_ns,

		figure = fn("figure", function(a, kw)
			active_fig = make_figure_instance(kw, a and a[1])
			return active_fig
		end),

		subplots = fn("subplots", function(a, kw)
			kw = kw or {}
			local nr = (a[1] ~= nil and not is_none(a[1])) and floor(MathEngine.unwrap_number(a[1])) or (kw.nrows and floor(MathEngine.unwrap_number(kw.nrows))) or 1
			local nc = (a[2] ~= nil and not is_none(a[2])) and floor(MathEngine.unwrap_number(a[2])) or (kw.ncols and floor(MathEngine.unwrap_number(kw.ncols))) or 1
			nr, nc = max(1, nr), max(1, nc)

			if is_none(kw.figsize) and (nr > 1 or nc > 1) then
				kw.width = max(420, nc * 260)
				kw.height = max(300, nr * 230)
			end
			local fig = make_figure_instance(kw)
			fig.__dict._state.nrows = nr
			fig.__dict._state.ncols = nc
			active_fig = fig

			local sub_kw = py_dict_to_lua(kw.subplot_kw)
			local ax_opts = {
				projection = sub_kw.projection or (not is_none(kw.projection) and RUtils.to_str(kw.projection, false)) or nil,
				polar = (sub_kw.polar == true) or (kw.polar == true),
				polar_shape = sub_kw.polar_shape or kw.polar_shape
			}

			if nr == 1 and nc == 1 then
				local ax = make_axes_instance(fig, 1, 1, ax_opts)
				return { __type = "tuple", elements = { fig, ax } }
			elseif nr == 1 or nc == 1 then
				local ax_list = {}
				local count = max(nr, nc)
				for idx = 1, count do
					local r = (nr == 1) and 1 or idx
					local c = (nc == 1) and 1 or idx
					ax_list[idx] = make_axes_instance(fig, r, c, ax_opts)
				end
				fig.__dict._current_ax = ax_list[1]
				return { __type = "tuple", elements = { fig, { __type = "list", elements = ax_list } } }
			else
				local rows_list = {}
				for r = 1, nr do
					local r_elts = {}
					for c = 1, nc do
						r_elts[c] = make_axes_instance(fig, r, c, ax_opts)
					end
					rows_list[r] = { __type = "list", elements = r_elts }
				end
				fig.__dict._current_ax = rows_list[1].elements[1]
				return { __type = "tuple", elements = { fig, { __type = "list", elements = rows_list } } }
			end
		end),

		subplot = fn("subplot", function(a, kw)
			local fig = ensure_fig(kw)
			local call_args = { fig }
			for i = 1, #(a or {}) do call_args[#call_args + 1] = a[i] end
			return get_figure_class().__dict.add_subplot.__call(call_args, kw)
		end),

		gca = fn("gca", function(_, kw) return ensure_ax(kw) end),
		gcf = fn("gcf", function(_, kw) return ensure_fig(kw) end),

		-- 繪圖指令轉發
		plot         = forward_to_ax("plot"),
		plot3D       = forward_to_ax("plot3D"),
		step         = forward_to_ax("step"),
		scatter      = forward_to_ax("scatter"),
		scatter3D    = forward_to_ax("scatter3D"),
		fill         = forward_to_ax("fill"),
		fill_between = forward_to_ax("fill_between"),
		stackplot    = forward_to_ax("stackplot"),
		bar          = forward_to_ax("bar"),
		barh         = forward_to_ax("barh"),
		broken_barh  = forward_to_ax("broken_barh"),
		hist         = forward_to_ax("hist"),
		pie          = forward_to_ax("pie"),
		boxplot      = forward_to_ax("boxplot"),
		violinplot   = forward_to_ax("violinplot"),
		errorbar     = forward_to_ax("errorbar"),
		imshow       = forward_to_ax("imshow"),
		pcolormesh   = forward_to_ax("pcolormesh"),
		matshow      = forward_to_ax("matshow"),
		plot_surface = forward_to_ax("plot_surface"),
		plot_wireframe = forward_to_ax("plot_wireframe"),
		complex_plot = forward_to_ax("complex_plot"),
		axhline      = forward_to_ax("axhline"),
		axvline      = forward_to_ax("axvline"),
		text         = forward_to_ax("text"),
		annotate     = forward_to_ax("annotate"),

		-- 標籤與座標設定轉發
		title        = forward_to_ax("set_title"),
		xlabel       = forward_to_ax("set_xlabel"),
		ylabel       = forward_to_ax("set_ylabel"),
		zlabel       = forward_to_ax("set_zlabel"),
		xlim         = forward_to_ax("set_xlim"),
		ylim         = forward_to_ax("set_ylim"),
		zlim         = forward_to_ax("set_zlim"),
		xticks       = forward_to_ax("set_xticks"),
		yticks       = forward_to_ax("set_yticks"),
		zticks       = forward_to_ax("set_zticks"),
		grid         = forward_to_ax("grid"),
		legend       = forward_to_ax("legend"),
		axis         = forward_to_ax("axis"),
		cla          = forward_to_ax("cla"),

		suptitle = fn("suptitle", function(a)
			local fig = ensure_fig()
			fig.__dict._state.suptitle = RUtils.to_str(a[1] or "", false)
			return B.None
		end),

		colorbar = fn("colorbar", function(a, kw)
			local fig = ensure_fig()
			return get_figure_class().__dict.colorbar.__call({ fig, a[1] }, kw)
		end),

		tight_layout = fn("tight_layout", function() return B.None end),

		clf = fn("clf", function()
			if active_fig then
				get_figure_class().__dict.clear.__call({ active_fig }, {})
			end
			return B.None
		end),

		close = fn("close", function(a)
			local target = a and unwrap_scalar(a[1])
			if target == nil then
				if active_fig then
					for i = #figure_queue, 1, -1 do
						if figure_queue[i] == active_fig then table.remove(figure_queue, i); break end
					end
				end
				active_fig = figure_queue[#figure_queue]
			elseif target == "all" then
				figure_queue = {}
				active_fig = nil
			else
				for i = #figure_queue, 1, -1 do
					local f = figure_queue[i]
					if f == target or f.__dict._num == target then
						table.remove(figure_queue, i)
						if active_fig == f then active_fig = figure_queue[#figure_queue] end
					end
				end
			end
			return B.None
		end),

		savefig = fn("savefig", function(a, kw)
			local fig = ensure_fig()
			return get_figure_class().__dict.savefig.__call({ fig, a[1] }, kw)
		end),

		show = fn("show", function(a)
			-- 若傳入單一 Figure 實例 plt.show(fig1),則只渲染該張圖
			if a and type(a[1]) == "table" and a[1].__class == FigureClass then
				return get_figure_class().__dict.show.__call({ a[1] }, {})
			end
			if #figure_queue == 0 and not active_fig then return "" end
			local targets = (#figure_queue > 0) and figure_queue or { active_fig }
			local out_list = {}
			for _, fig in ipairs(targets) do
				if not fig.__dict._shown and #(fig.__dict._state.axes_list or {}) > 0 then
					local svg_out = SVGRenderer.renderFigure(fig.__dict._state)
					out_list[#out_list + 1] = svg_out
					if Runtime.builtins and Runtime.builtins.print then
						Runtime.builtins.print({ svg_out }, {})
					else
						table.insert(Runtime.stdout, svg_out .. "\n")
					end
				end
			end
			figure_queue = {}
			active_fig = nil
			return table.concat(out_list, "\n")
		end)
	}

	local plt_mod = Library.make_namespace("matplotlib.pyplot", plt_dict)
	mod_cache["matplotlib.pyplot"] = plt_mod
	mod_cache["pylab"] = plt_mod

	local mpl_mod = Library.make_namespace("matplotlib", {
		pyplot = plt_mod,
		style = style_ns,
		cm = cm_ns,
		ticker = ticker_ns,
		Figure = FigureClass,
		Axes = AxesClass,
		__version__ = "3.9.0-wiki-hybrid"
	})
	plt_dict.pyplot = plt_mod
	mod_cache["matplotlib"] = mpl_mod

	return mod_cache[name] or plt_mod
end

Matplotlib.init_matplotlib = Matplotlib.get_module

return Matplotlib