前回のあらすじ
SVGを使って図形を描く試みは「放物線もどき」のところまで進んだが、「手描き」では限界があることを確認。より滑らか放物線を描くべく、pythonを使った「自動生成」を試みることにした。これが今回のテーマである。
pythonで自動生成プログラムを書く
プログラムといっても実に簡単なプログラムである。$y=x^2$の数値をSVGの文法に則って書き出すだけである。つまり数値計算の難しさというよりは、文字列の扱いの方が今回のメインテーマということになる。とはいえ、難しい要素はほとんどない。一方、吐き出されたSVGプログラムには多数の数字が含まれていて、非常に複雑に見えるはずである。とりわけ計算のメッシュポイントを増やすとかなりかさばるだろう。しかし、内容は単純であり、実用上はそれをただただコピペして、hatena blogのmarkdown形式の編集窓に貼り付けるだけである。
多分、AIと呼ばれるソフトウェアも、結局はこのような吐き出しプログラムと構造的には大した違いはないのであろう。ただ、場合分けがあまりにも多いので、CPUパワーやメモリのサイズ、そしてプログラム中に例外処理を書き出す手間暇、といったところが想像以上の複雑さを持っているはずである。AIについてはまた別の機会に考察することにしたい。
ではさっそくプログラムを書いてみよう。まずは、svgの「最外殻」に相当する部分の書き出しである。 描画領域はあとあとで変更できるように「変数」として定義してみよう。
#!/usr/local/bin/python3 XSVG=300 YSVG=300 print ('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="',XSVG,'" height="',YSVG,'" id="parabola" >') print ('</svg>')
このpythonプログラムの出力結果は以下のようになる。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width=" 300 " height=" 300 " id="parabola"> </svg>
実際のSVGの表現は下のようによる。(ただし、わかりやすいように、領域いっぱいに四角形を置いてみた。)
機能上は全く問題ないのだが、widthとheightの引用符の中に変数として書き込んだ部分に空白が入り込んでしまい、ちょっと見栄えがよくない。文字処理の問題である。これをなんとかしてみよう。
pythonにおける文字列の連結
参考にしたのはこちらのHPである。
ポイントは、printの出力の中に文字列型変数と整数型変数が混在している点である。SVGのプログラムとして書き出すので、整数のままにしておく必要はなく、むしろ文字列として数字を書き出してしまった方が統一性が高くなる。数字を文字列に変換するにはstr関数を使う。
次に、文字列化した数字を他の文字列に連結する。このためには連結のための演算子「+」を使う。つまり、
'<svg width="'+str(XSVG)+'">
とやると、空白が入らない状態で文字列と数字が連結される。
またprint文の中で連結をするとプログラムが見にくなるので、この機会に文字列変数を導入して文字列に対する演算を外側で処理してみよう。
#!/usr/local/bin/python3 XSVG=300 YSVG=300 svg0 = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"' svg1 = ' width="' svg2 = '" height="' svg3 = '" id="parabola">' svg0 += svg1 + str(XSVG) + svg2 + str(YSVG) + svg3 print(svg0) print('</svg>')
このプログラムの出力は次のようになる。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300" height="300" id="parabola"> </svg>
数字「300」が空白を挟まずに引用符で挟まれるようになった。成功である。
format関数とformatメソッドという方法もあるようだが、今回は+演算子とstr関数のみで「十分」としておこう。
座標軸と円を置く
まずは座標系を書き込んでみよう。lineコマンドを使うだけだが、その位置を描画領域の真ん中に置きたい(つまり原点を領域の真ん中にしたい)ので、XSVGとYSVGという変数を利用した表現にしてみよう。とはいえ、上で覚えたばかりのやり方を早速使ってみるだけである。
#!/usr/local/bin/python3 XSVG=300 YSVG=300 # Most outer shell in SVG structure svg0 = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"' svg1 = ' width="' svg2 = '" height="' svg3 = '" id="parabola">' svg0 += svg1 + str(XSVG) + svg2 + str(YSVG) + svg3 # Axes (x + y) ## x-axis svgXax0 ='<line x1="0" y1="' + str(YSVG // 2) + '" x2="' + str(XSVG) + '" y2="' + str(YSVG // 2) svgXax1 ='" stroke="black" stroke-width="2" id="xaxis" />' print(svg0) print(svgXax0 + svgXax1) print('</svg>')
出力結果は下のようになり、
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300" height="300" id="parabola"> <line x1="0" y1="150" x2="300" y2="150" stroke="black" stroke-width="2" id="xaxis" /> </svg>
その対応図形が下の図である。
pythonのプログラムで着目する点は、整数同士の割り算の結果が整数で閉じるように、割り算の演算を「//」にしてある点である。単に「/」とすると小数(実数)形式で印字される。実用上は問題ないのであるが、趣味の問題としてここでは//の演算子を利用してみた。
同様の書式でy軸を描きこむと下のようになる。
円に関しても、同様の処理でうまくいく。ただ、y軸に沿って円の中心を自由に設定したいので、その部分を変数$a$で解決する。とはいえ、まずは原点に円を置いてみる。
#!/usr/local/bin/python3 XSVG=300 YSVG=300 # Most outer shell in SVG structure svg0 = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"' svg1 = ' width="' svg2 = '" height="' svg3 = '" id="parabola">' svg0 += svg1 + str(XSVG) + svg2 + str(YSVG) + svg3 # Frame of the drawing region svgFrame0 = '<rect x="0" y="0" width="' + str(XSVG) + '" height="' + str(YSVG) svgFrame0 += '" fill="none" stroke="black" stroke-width="1" />"' # Axes (x + y) ## x-axis svgXax0 = '<line x1="0" y1="' + str(YSVG // 2) + '" x2="' + str(XSVG) + '" y2="' + str(YSVG // 2) svgXax1 = '" stroke="black" stroke-width="2" id="xaxis" />' ## y-axis svgYax0 = '<line x1="'+ str(XSVG // 2) + '" y1="0" x2="' + str(XSVG // 2) + '" y2="' + str(YSVG) svgYax1 = '" stroke="black" stroke-width="2" id="yaxis" />' # cirlce at (0,a) svgCirc0 = '<circle cx="' + str(XSVG // 2) + '" cy="' + str(YSVG // 2) svgCirc0 += '" r="100" stroke="black" stroke-width="3" fill="none" />' print(svg0) print(svgFrame0) print(svgXax0 + svgXax1) print(svgYax0 + svgYax1) print(svgCirc0) print('</svg>')
このプログラムを走らせると次の結果を得る。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300" height="300" id="parabola"> <rect x="0" y="0" width="300" height="300" fill="none" stroke="black" stroke-width="1" />" <line x1="0" y1="150" x2="300" y2="150" stroke="black" stroke-width="2" id="xaxis" /> <line x1="150" y1="0" x2="150" y2="300" stroke="black" stroke-width="2" id="yaxis" /> <circle cx="150" cy="150" r="100" stroke="black" stroke-width="3" fill="none" /> </svg>
これを実行すると下の図となる。
円の中心の座標を$(0,a)$とし、$a$を変数としてプログラムに導入する。 $a=50$を試しにセットして、円を上方にずらしてみた。
#!/usr/local/bin/python3 # Drawing region XSVG=300 YSVG=300 # Position of the centre of the circle (0,a) a=50 ..... # cirlce at (0,a) svgCirc0 = '<circle cx="' + str(XSVG // 2) + '" cy="' + str(YSVG // 2 - a) svgCirc0 += '" r="100" stroke="black" stroke-width="3" fill="none" />'
いよいよ放物線
放物線は$y=x^2$であるが、この公式をそのまま利用することはできない。SVGの座標と理論上の座標の変換が必要だからである。いま、我々の座標系は描画領域の真ん中にセットしたのでSVG座標の$\boldsymbol{O}=(\text{XSVG}/2, \text{YSVG}/2)$が「原点」である。
また、y軸の方向がSVG座標と我々の座標では反対向きになっている。
以上の二点を考慮すると、SVG座標$\boldsymbol{r}_s=(x_s,y_s)$と我々の座標$\boldsymbol{r}=(x,y)$は、次の変換によって結ばれていることがわかる。
\begin{equation} \boldsymbol{r}_s=\boldsymbol{O}+\begin{pmatrix}1 & 0 \\ 0 & -1\\ \end{pmatrix}\boldsymbol{r} \end{equation}
理論上の$(x,y=x^2)$を計算し、それを$(x_s,y_s)$に変換してから出力するとSVG放物線となるのである。
もしグラフの見栄えを気にするなら、上の行列にスケール因子$S$をかけておいてもいいだろう。
ということで、$S=50$の場合、つまり理論座標で$x=-XSVG/(2*S) = -3$から$x=3$までの範囲で放物線を描くプログラムは次のようになった。ちなみに計算ステップは3000であるが、300にすると当然ながら「ガタガタ」になる。
#!/usr/local/bin/python3 def convSVG(xp,isgn,Xo,scale): xa = Xo + isgn * scale * xp return xa # Drawing region XSVG=300 YSVG=300 # Position of the centre of the circle (0,a) a=.75 # Theoretical range for parabola Xmin = -150 Xmax = 150 Scale = 50 #Calc. step for parabola Nmax = 3000 dx = (Xmax - Xmin) / Nmax # Most outer shell in SVG structure svg0 = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"' svg1 = ' width="' svg2 = '" height="' svg3 = '" id="parabola">' svg0 += svg1 + str(XSVG) + svg2 + str(YSVG) + svg3 # Frame of the drawing region svgFrame0 = '<rect x="0" y="0" width="' + str(XSVG) + '" height="' + str(YSVG) svgFrame0 += '" fill="none" stroke="black" stroke-width="1" />"' # Axes (x + y) ## x-axis svgXax0 = '<line x1="0" y1="' + str(YSVG // 2) + '" x2="' + str(XSVG) + '" y2="' + str(YSVG // 2) svgXax1 = '" stroke="black" stroke-width="2" id="xaxis" />' ## y-axis svgYax0 = '<line x1="'+ str(XSVG // 2) + '" y1="0" x2="' + str(XSVG // 2) + '" y2="' + str(YSVG) svgYax1 = '" stroke="black" stroke-width="2" id="yaxis" />' # cirlce at (0,a) svgCirc0 = '<circle cx="' + str(XSVG // 2) + '" cy="' + str(convSVG(a,-1, YSVG/2,Scale)) svgCirc0 += '" r="100" stroke="black" stroke-width="3" fill="none" id="circle0" />' # parabola svgParabola = '<path d="M ' xp = Xmin for i in range(Nmax + 1): xs = convSVG(xp, 1, XSVG/2, Scale) ys = convSVG(xp**2, -1, YSVG/2, Scale) svgParabola += str(xs)+','+str(ys)+' L ' xp += dx svgParabola += '" stroke="black" stroke-width="3" fill="none" id="parabola"/>' print(svg0) print(svgFrame0) print(svgXax0 + svgXax1) print(svgYax0 + svgYax1) print(svgCirc0) print(svgParabola) print('</svg>')
出力は予想通り数字の羅列である。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300" height="300" id="parabola"> <rect x="0" y="0" width="300" height="300" fill="none" stroke="black" stroke-width="1" />" <line x1="0" y1="150" x2="300" y2="150" stroke="black" stroke-width="2" id="xaxis" /> <line x1="150" y1="0" x2="150" y2="300" stroke="black" stroke-width="2" id="yaxis" /> <circle cx="150" cy="112.5" r="100" stroke="black" stroke-width="3" fill="none" /> <path d="M -7350.0,-1124850.0 L -7345.0,-1123350.5 L -7340.000000000001,-1121852.0000000002 L -7335.000000000001,-1120354.5000000002 \ L -7330.000000000001,-1118858.0000000005 L -7325.000000000002,-1117362.5000000005 L -7320.000000000002,-1115868.0000000005 L -7315.00\ 0000000002,-1114374.5000000007 L -7310.000000000002,-1112882.0000000007 L -7305.000000000003,-1111390.5000000007 L -7300.000000000003\ ,-1109900.000000001 L -7295.000000000003,-1108410.500000001 L -7290.000000000004,-1106922.000000001 L -7285.000000000004,-1105434.500\ 000001 L -7280.000000000004,-1103948.0000000012 L -7275.000000000005,-1102462.5000000012 L -7270.000000000005,-1100978.0000000014 ....(省略)
完全なものを貼り付けると、綺麗な放物線が現れる。