複天一流:どんな手を使ってでも問題解決を図るブログ

宮本武蔵の五輪書の教えに従い、どんな手を使ってでも問題解決を図るブログです(特に、科学、数学、工学の問題についてですが)

東京大学2023数学[3] part-7: svgで放物線を描く(でも手描きで)

前回のあらすじ

hatenablogに関数グラフをSVGで描く試みを始めた。座標軸を表す直線や囲みに対応させた四角形、そして円などは、SVGに最初から組み込まれ準備されている図形であり、文法通りにコマンドを書くだけで望みの図形を描くことができた。

しかし、問題文にあった「放物線」に関しては、組み込まれたコマンドがないため、折れ線をつなぎ、自分でプログラミングするほかはない。折れ線はpathコマンドを使うのだが、どうやってそれを実現するのか。今回はこの問題を「複天一流」の精神で解決しようと思う。

pathで放物線もどきを「手で」描く

pathの文法はpostscriptとよく似た感じである。まずは「ペン先」の移動(move)のコマンドとしてM, そして線引き(line)のコマンドとしてL、そして図形を閉じるためのZコマンドの3つが基本だと思う。今回は閉曲線を描かないので(放物線は開いた図形)、Zは利用しない。

描画点を表す座標は定義領域の左上が原点なので、高校の教科書のように領域の真ん中に原点を置きたいときは、座標変換する必要があるが、まずは練習を兼ねてpathコマンドを使ってみよう。

まずは、放物線$y=x^2$の「右半分」つまり$x\ge 0$の領域を描いてみたい。とはいえ、「手書き」なので綺麗な放物線は描けない。ガタガタした形の「放物線もどき」である。もっとも大雑把な放物線として二点のみによる近似をやってみよう。ここでは(理論座標として)$x=0$と$x=1$の二点のみとする。放物線の式にこの座標を代入すると、$(x,y)=(0,0), (1,1)$となる。つまり原点とそれ以外の点の二点に対応する。

最初に、「ペン先」を原点に移動する必要がある。この命令は、

<path d='M 0,0'>

とやりたいところである。しかし、座標変換をするのを忘れている。SVGの描画領域はいま

<svg width='300' height='300' ....>

と定義してある。もしこの領域の真ん中に原点をもってきたいとするならば、それは(150, 150)へと変換する必要がある。 つまり、

理論: (0,0) $\leftrightarrow$ SVG: (150,150)

である。したがって、

<path d='M 150,150'>

となる。これだけだと、ペン先を「原点」に置いただけなので何も描画されない。

次に、原点に置いたペン先を(1,1)まで動かして線を引くことを考える。

今、理論目盛り1に対応すSVG目盛りを50と仮定する。描画領域はx、y共に300の大きさがSVG座標ではあるので、理論目盛りにして6ユニット分の領域があることがわかる。正負の領域を考えるので、つまり-3から+3までの理論座標が設定されていることになる。理論座標で(1,1)は$$(1,1)\rightarrow (150 + 50, 150 + 50)=(200, 200)$$であるから

<path d='M 150,150 L 200,200' stroke='black' stroke-width='3'/>

となる。ペンの色と太さを指定したことに注意。これを実行してみると

「あれ? 第一象限にくるはずが、第4象限に「放物線の部分」が現れているぞ!」という問題が発生した。これはy軸の向きがSVG座標と理論座標で反転関係にあることに起因する「誤り」である。これを修正すると $$(1,1)\rightarrow (150 + 50, 150 - 50)=(200, 100)$$となる。つまり、

  <path d='M 150,150 L 150,100' stroke="black" stroke-width="3" id="parabola"/>
</svg>

と修正しなくてはならない。実行すると、

やっとうまくいった。やはり座標変換が面倒である。しかし手でやると決めたからには、まだまだここで音を上げてはならない。

次は$x=2, y=2^2=4$まで描こう。ペン先はすでに(1,1)にあるのだから、続けて(2,4)までLすればよい。座標変換は $$ (2,4) \rightarrow (150 + 2\times 50, 150 - 4\times 50) = (250, -50)$$となるが、この点は描画領域の外側になる。 気にしないでコマンドを続けると

<path d='M 150,150 L 150,100 L 250,-50' stroke='black' stroke-width='3' id='parabola' />

となる。

なにやら仮面ライダーの角のような黒い形が現れた....。これはデフォルトで「3角形の内部を塗りつぶす」という設定になっているからである。これをオフにするには

<path d='M 150,150 L 150,100 L 250,-50' stroke='black' stroke-width='3' fill='none' id='parabola' />

とすればよい。

定義領域まで描画されていることが確認できた。まずはこれで第一目標クリアである。

次は、$x<0$の領域に拡張しよう。ペン先は(2,4)にあるので、原点にまずは戻す。そして左右対称であることを利用して(-1,1), (-2,4)へとペンを順番に動かしていく。まとめると、

<path d='M 150,150 L 150,100 L 250,-50 M 150,150 L 100,100 L 50,-50' stroke='black' stroke-width='3' fill='none' id='parabola' />

となる。

かなり「ガタガタ」だが、3点でサンプリングした「放物線」が描けた。

もう少し滑らかにするためにさらに二点サンプリング点をつけ足そう。$(\pm 1/2, 1/4)$である。SVG座標では $$(150 \pm 50/2, 150 - 50/4)= (175, 137.5), (125,137.5)$$となる。したがって、

<path d='M 150,150 L 175,137.5 L 200,100 L 250,-50  M 150,150 L 125,137.5 L 100,100 L 50,-50'  stroke='black' stroke-width='3' fill='none' id='parabola' />

とやると、多少滑らかになる。

手でやるとこのようになるが、これを自動化して仕舞えば、いくらでも細かいサンプリングレートを設定して放物線を描かせることができる。座標変換の部分で多少数学を使わないといけないが、平行移動とパリティ反転だけなので、それほど難しくない。問題は「自動化」をどうやってやるかだが、今回はpythonを使って実現した。

次回はpythonを使って「自動化」したsvgの放物線を描く試みに挑戦する。