Logo address

SVG Path

目次

SVG Path

SVG の Path は守備範囲が広い。万屋である。
折れ線、ベジェ曲線、閉じた折れ線、閉じたベジェ曲線が描ける。

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

このプログラムのコードは

#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 流の方が分かりやすくて僕の好みである。

3次ベジェ曲線

2012/12/02
2012/12/04 改訂

例1

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。
図1. 3次ベジェ曲線

#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()

今回は、新たに次の機能をサポートした。

grid() の label は、細かなチューニング(文字フォント、文字の大きさ、位置など)をサボートしていない。この部分は好みに依存する部分が大きく、まじめにやる価値は殆どない。必要とあれば、直接 text() method で書けばよいし、その事は面倒ではない。
T class は、table データのクラスであり、データの一次元配列、2次元配列をサポートする。(内部構造は次元依存性を持たないが動作の確認が必要である。) 2項演算は、(vector や matrix と異なり)要素毎の演算として定義されている。
mark() は、指定された座標に symbol と(必要なら) label を付ける。label を付けた場合には、anchordxdy でラベルの位置を指定できる。座標は複数指定でき、labelahchordxdy はリストあるいはタプルで与える事ができる。

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,...]
と簡潔に書くことになる。(cal() は既に table module でサポートとされている)
でも世の中、逆ポーランドの演算を知らない人が多いからね...

例2

線分P[0]P[3]が線分P[1]P[2]に平行な場合には、幾何学的な考察を行いやすい。

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

さて、この場合には、t=0.5 で次の図になる。

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

このことから、この曲線の最大の高さは、台形P[0],P[1],P[2],P[3]の高さの 3/4 であることが解る。

例3

t=0.4 とし、path を

	P = [(0, 0), (1, 2), (1, 2), (2, 0)]
で与えた場合、

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

この場合には3つの点、BP[1]P[2] が縮退する。

t=0.5 では

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

従って頂点の高さは、Bの高さの 3/4 である。

例4

次に、t=0.4

	P = [(0, 0), (1, 2), (1, -2), (2, 0)]
とした場合

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

例5

t=0.4 で、P[2] = P[3] のケース。つまり、

	P = [(0, 0), (1, 2), (2, 0), (2, 0)]

あなたのブラウザは SVG をサポートしていません。最新のブラウザをお使いください。

生成された曲線は、左右対称のように見えるけど、本当か?