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)]
生成された曲線は、左右対称のように見えるけど、本当か?