模組:沙盒/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