參數、網格與動畫
參數與運算式
圖層座標不一定是數字,也可以是由具名參數組成的運算式。此時,場景描述一族圖形,每組參數值對應一張圖;綁定參數就是從中選出一張。
Parameter(name, value_type=None)
具名的佔位符。設定 value_type 後,綁定值必須是該型別的實例;所有綁定值都必須可雜湊。values(seq) 回傳 ParameterValues,並立即檢查每個值。
Constant(value)
不可變的運算式樹。參數與常數可以透過 +、-、*、/、**、一元 -,以及比較運算 <、<=、>、>= 與 equals() 組合;一般數字會包裝成常數。運算式採結構相等:== 比較兩棵運算式樹,要建立比較運算式時應改用 equals()。把運算式當成真假值使用會引發 BindingError;使用前須先求值。evaluate(bindings) 根據參數到值的對應計算結果;free_parameters() 回傳樹中所有參數的集合。
定義 · 運算式與綁定
運算式是常數、參數,或對運算式 \(e_1, e_2\) 與運算 \(\circ\) 而言的 \(e_1 \circ e_2\)。其自由參數為 \(\text{free}(c) = \emptyset\)、\(\text{free}(p) = \lbrace p\rbrace\)、\(\text{free}(e_1 \circ e_2) = \text{free}(e_1) \cup \text{free}(e_2)\)。綁定 \(\beta\) 是從參數到值的有限對應,而 \(\text{bind}(e, \beta)\) 定義為:若 \(\text{free}(e) \subseteq \text{dom} \beta\),則為值 \(e(\beta)\);否則若 \(e\) 為參數,則為 \(e\) 本身;否則為 \(\text{bind}(e_1, \beta) \circ \text{bind}(e_2, \beta)\)。一般值 \(v\) 沒有自由參數,求值結果為其本身。
定理 · 部分綁定
對每個運算式 \(e\) 與綁定 \(\beta\), (i) \(\text{free}(\text{bind}(e, \beta)) = \text{free}(e) \setminus \text{dom} \beta\); (ii) 對每個滿足 \(\text{dom} \gamma \cap \text{dom} \beta = \emptyset\) 且 \(\text{dom} \gamma \supseteq \text{free}(e) \setminus \text{dom} \beta\) 的綁定 \(\gamma\),\(\text{bind}(e, \beta)(\gamma) = e(\beta \cup \gamma)\)。
推論 · 分階段綁定
對定義域不相交的綁定 \(\beta_1, \beta_2\),\(\text{bind}(\text{bind}(e, \beta_1), \beta_2)\) 與 \(\text{bind}(e, \beta_1 \cup \beta_2)\) 有相同的自由參數,且在這些參數的每個綁定下有相同的值。特別地,canvas.bind(p, 1).bind(q, 2) 與 canvas.bind({p: 1, q: 2}) 畫出相同的圖。
bind 會走訪 tuple、mapping 與所有 mosaickit dataclass,因此能一次綁定整個場景;圖層的 model 則保持不變。若繪製的場景仍含自由參數,會引發指名這些參數的 BindingError。
from mosaickit import Canvas, Parameter, TextLayer
x = Parameter("x", value_type=float)
template = Canvas().add(TextLayer((x, 2 * x + 1), "moving"))
frame = template.bind(x, 3.0)
# (3.0, 7.0)
print(frame.snapshot().layers[0].position)
網格
CanvasGrid(
cells,
rows=None,
cols=None,
shape=None,
links=()
)
Span(
canvas,
rows=1,
cols=1
)
在同一張圖中放入多個畫布。cells 可以是由畫布、Span 與 None(空格)組成的平面清單,並逐列填入;也可以是列的清單,此時允許跨列,且每列必須涵蓋所有欄。shape=(rows, cols) 等同於同時傳入兩者。每個畫布都會複製,因此之後修改原畫布不會影響網格。跨格重疊、跨格超出邊界、格子過多,或混用平面與巢狀格子,都會引發 ConfigurationError。render() 與 save() 的用法與畫布相同。
命題 · 推斷的網格形狀
對 \(n \ge 1\) 個一般格子組成的平面清單,若未給 rows 與 cols,網格有 \(c = \left\lceil \sqrt\lbrace n\rbrace \right\rceil\) 欄、\(r = \left\lceil n / c \right\rceil\) 列。此時 \(r c \ge n\)、\(r \le c\),且空格少於 \(c\) 個,全部位於最後一列。
只給 cols 時 \(r = \left\lceil n / c \right\rceil\);只給 rows 時 \(c = \left\lceil n / r \right\rceil\)。
CanvasGrid.from_layout(canvases, layout)
常見的排列:SINGLE、STACKED(兩列)、SIDE_BY_SIDE、GRID_2X2、GRID_3X3,以及三個畫布的 TOP_TWO_BOTTOM_ONE 與 TOP_ONE_BOTTOM_TWO,其中單獨的畫布橫跨兩欄。
CanvasGrid.sweep(
template,
values,
*,
cols=None
)
ParameterValues 的每個值一格,每格是綁定該值的範本。
GridLink(
start_cell,
start,
end_cell,
end,
role="link",
stroke=None
)
從某格的 start 畫到另一格 end 的直線;兩點分別以所在格子的資料座標表示。直線畫在整張圖上,會橫越格子間的空隙。格子依配置順序編號。樣式先在起點格子的主題中解析 role,再疊上 stroke。格子編號超出範圍時,建立網格就會引發 ConfigurationError。
from mosaickit import (
Canvas,
CanvasGrid,
GridLink,
MarkerLayer,
Parameter,
Stroke,
)
shift = Parameter("shift", value_type=float)
template = Canvas().add(MarkerLayer([(5, 2.5 + shift)]))
cells = [template.bind(shift, v) for v in (0.0, 2.0, 4.0)]
link = GridLink(
0, (5, 2.5), 2, (5, 6.5), stroke=Stroke(dash="dashed")
)
CanvasGrid(cells, cols=3, links=[link]).save("sweep.pdf")
動畫
每個值對應一個畫面,由範本綁定該參數值而成。frames() 逐一產生與繪製器無關的場景;save(path) 寫出 GIF(透過 Pillow)或 MP4(透過 ffmpeg)。建立動畫時就會依參數檢查各值;值清單為空或 fps 非正時,會引發 ConfigurationError。
from mosaickit import Animation
values = shift.values([0.0, 1.0, 2.0, 3.0])
Animation.sweep(template, values, fps=4).save("sweep.gif")