SVG の Path は守備範囲が広い。万屋である。
折れ線、ベジェ曲線、閉じた折れ線、閉じたベジェ曲線が描ける。
この SVG ファイルを生成する Python のコードは
#encoding: utf-8
import svg
c = svg.Canvas(0,0,600,500,map=(-1.5,1.5,1.5,-1.5))
c.grid(1,1,stroke="cyan")
c.text(1,1,text="(1,1)",anchor="NW")
c.text(-1,1,text="(-1,1)",anchor="NE")
c.text(0,-1,text="(0,-1)",anchor="NW")
c.path("M -1 1 L 0 -1 1 1", stroke="black;dasharray:4 4")
c.path("M -1 1 Q 0 -1 1 1")
c.path("M -1 1 S 0 -1 1 1",stroke="red")
c.path("M -1 1 C 0 -1 0 -1 1 1",stroke="blue")
c.close()
このライブラリの特徴は、座標を論理座標で描けることである。
ここに現れる path 関数は、この SVG Library の中では low level の関数で、SVG の仕様がむき出しになっている。(論理座標への変換は行っている)
ここに示した図は L,Q,S,C の意味を捉えるには良いであろう。このうち、黒の線で示した Q は放物線のセグメントである。
ところで、文字列を書く位置の指定は、このプログラムのように、Python/Tk 流の方が分かりやすくて僕の好みである。
補注: www.w3schools.com には
SVG Path
https://www.w3schools.com/graphics/svg_path.asp
この SVG ファイルを生成する Python のコードは
#encoding: utf-8
import svg
from table import *
c = svg.Canvas(0,0,500,500,map=(-1,3,3,-1))
c.grid(1,1,stroke="cyan",range=(-0.5,2.5,2.5,-0.5),label=("x","y"),pad=0.2)
mblack = c.symbol("circle",fill="black")
mgreen = c.symbol("circle",fill="green")
mred = c.symbol("circle",fill="red")
mblue = c.symbol("circle",fill="blue")
P = [(0, 0), (1, 2), (2, 1.5), (2, 0)]
lab=("P[0]","P[1]","P[2]","P[3]")
c.path("M %s %s C %s %s %s %s %s %s"%tuple(svg.expand(P)),stroke="width:2")
anchor = ("NE","SE","W","NE")
c.line(P,stroke="green")
c.mark(P,ref=mblack,label=lab,anchor=anchor,pad=0.5)
t = 0.4
for n in range(0,len(P)):
P[n] = T(*P[n])
A = (1-t)*P[0]+t*P[1]
B = (1-t)*P[1]+t*P[2]
C = (1-t)*P[2]+t*P[3]
M = (1-t)*A+t*B
N = (1-t)*B+t*C
Q = (1-t)*M+t*N
L = [A.value(),B.value(),C.value()]
c.line(L)
c.mark(L,ref=mgreen,label=("A","B","C"),anchor=("SE","SW","W"),pad=0.2)
L = [M.value(),N.value()]
c.line(L)
c.mark(L,ref=mred,label=("M","N"),anchor="S",dy="-0.5")
c.mark(Q.value(),ref=mblue,label="Q",anchor="NW")
c.close()
今回は、新たに次の機能をサポートした。
svg module の grid() で labeltable module の T class で、混合演算svg module の Canvas class に mark() method を追加したgrid() の label は、細かなチューニング(文字フォント、文字の大きさ、位置など)をサボートしていない。この部分は好みに依存する部分が大きく、まじめにやる価値は殆どない。必要とあれば、直接 text() method で書けばよいし、その事は面倒ではない。T class は、table データのクラスであり、データの一次元配列、2次元配列をサポートする。(内部構造は次元依存性を持たないが動作の確認が必要である。) 2項演算は、(vector や matrix と異なり)要素毎の演算として定義されている。mark() は、指定された座標に symbol と(必要なら) label を付ける。label を付けた場合には、anchor、dx、dy でラベルの位置を指定できる。座標は複数指定でき、label、ahchor、dx、dy はリストあるいはタプルで与える事ができる。
T は本当に必要なのかと考えたりもする。新たなクラスを作った場合には、その値を取り出すのに
A.value()
line() が T class を認識していれば、この問題は line() のコードの中で解決するが、今のところ外付けである。)
逆ポーランドの演算を使えば、T class は要らない。その場合、
for n in range(0,len(P)): P[n] = T(*P[n]) A = (1-t)*P[0]+t*P[1] ... L = [A.value(),...]
A = cal((1-t),P[9],"*",t,P[1],"*","+") L = [A,...]
table module でサポートとされている)
線分P[0]P[3]が線分P[1]P[2]に平行な場合には、幾何学的な考察を行いやすい。
さて、この場合には、t=0.5 で次の図になる。
このことから、この曲線の最大の高さは、台形P[0],P[1],P[2],P[3]の高さの 3/4 であることが解る。
t=0.4 とし、path を
P = [(0, 0), (1, 2), (1, 2), (2, 0)]
この場合には3つの点、B、P[1]、P[2] が縮退する。
t=0.5 では
従って頂点の高さは、Bの高さの 3/4 である。
次に、t=0.4 で
P = [(0, 0), (1, 2), (1, -2), (2, 0)]
t=0.4 で、P[2] = P[3] のケース。つまり、
P = [(0, 0), (1, 2), (2, 0), (2, 0)]
生成された曲線は、左右対称のように見えるけど、本当か?