03 関数のグラフと曲線
この節では,関数のグラフを描くにあたって,座標軸や方眼を描く関数を作ったり,ベジェ曲線を描いたりする方法を示します。
座標軸
Cinderellaの画面では,座標軸のツールを用いて背景に座標軸を表示できますが,これとは別に細い線で座標軸を表示したいこともあるでしょう。背景なので,背景色によっては見にくくなることもあります。
座標軸を描くには2つの方法があります。
(1) 線分ツールで線を引き,インスペクタで矢じりをつける。
簡単な方法であるのと,点を軸上に置く(軸上だけを動くようにする:インシデント)のも簡単です。
ただ,インスペクタで両端の点を非表示にしたりするのが手間といえば手間です。(それでも,スクリプトを書くよりは簡単)
(2) Cindyscriptで関数を作る。
関数を作ってしまえば簡単に描けます。
このような関数です。
// 軸表示 引数は x,y の範囲(リスト) hsize 矢じりサイズ
drawaxis(rangex,rangey):=drawaxis(rangex,rangey,0.4);
drawaxis(rangex,rangey,hsize):=(
regional(x0,x1,y0,y1,col);
col=[0,0,0]; // 線の色
x0=rangex_1; // 軸の範囲
x1=rangex_2;
y0=rangey_1;
y1=rangey_2;
draw([[x0,0],[x1,0]],color->col);
draw([[0,y0],[0,y1]],color->col);
arrowhead([x1,0],[1,0],hsize,col);
arrowhead([0,y1],[0,1],hsize,col);
drawtext([x1,0]+[0.2,-0.1],"x",size->fsize);
drawtext([0,y1]+[-0.1,0.2],"y",size->fsize);
);
ここで,arrowhead() は「02 幾何図形の描画」で作ったユーザ定義関数です。
drawaxis([-5,5],[-5,5],0.4)
のようにして使います。線の色は引数に入っていません。黒以外にする場合は,colの値を変えます。
なお,矢じりをつけるかどうかをオプションで決められるようにすることもできます。「道具箱」にある drawaxis() はそのようになっています。
座標軸の目盛
座標軸を線分ツールで描いても,上のユーザ定義関数で描いても,目盛はついていません。
そこで,目盛りをつける関数を作りますが,画面のスケールが変わってもいいように,1目盛の単位も引数で指定できるようにしましょう。画面のスケールは取得できないので自動で変えることはできません。
さらに,x軸については,πも単位として使えるようにしておきます。
これだけのことを行うので,スクリプトはちょっと複雑になります。
axisscale(rangex,rangey,unit):=(
regional(fsize,pch,x0,x1,y0,y1,ux,uy,st,sn,str);
fsize=14; // フォントサイズ
pch=0.4; // 目盛を書くときのすきま
x0=rangex_1;
x1=rangex_2;
y0=rangey_1;
y1=rangey_2;
ux=unit_1; // 目盛の単位
uy=unit_2;
if(ux!=pi,
st=-floor(-x0/ux)*ux;
sn=floor(-x0/ux)+floor(x1/ux)+1;
repeat(sn,start->st,step->ux,
if(#!=0,
draw([#,-0.05],[#,0.05],color->col);
drawtext([#,-pch],#,align->"center",size->fsize);
);
);
, // x軸の単位がπのとき 目盛は pi/2 ごとに 数字はπごとに
st=-floor(-x0/ux);
sn=floor(-x0/ux)+floor(x1/ux)+1;
repeat(sn*2-1,start->st,step->0.5,
if(#!=0,draw([#*pi,-0.05],[#*pi,0.05],color->col));
);
repeat(sn,start->st,
if(#!=0,
if(#==-1,str="$-\pi$");
if(#==1,str="$\pi$");
if(|#|!=1,str=#+"$\pi$");
drawtext([#*pi,-pch],str,align->"center",size->fsize);
);
);
);
// y軸
st=-floor(-y0/uy)*uy;
sn=floor(-y0/uy)+floor(y1/uy)+1;
repeat(sn,start->st,step->uy,
if(#!=0,
draw([-0.05,#],[0.05,#],color->col);
drawtext([-pch/2,#-pch/2],#,align->"right",size->fsize);
);
);
// 原点O
drawtext([-pch,-pch],"O",size->fsize);
);
目盛の単位をデフォルト[1,1] として指定しなくてもよいように
axisscale(rangex,rangey):=axisscale(rangex,rangey,[1,1]);
も作っておきましょう。
必要な目盛の数字だけ入れる
前項では、指定した範囲にすべて目盛と数字をいれるように関数を作りました。しかし,実際にはその必要がないことが多いのではないでしょうか。
x軸とy軸のスケールが異なる場合もあり,前項の関数では対応できません。
たとえば,高等学校の数学の教科書では細かい目盛は入っていないのが普通です。必要なところにだけ数字を入れています。
したがって,状況に応じて、必要なところに数字を入れるというのが現実的でしょう。
次は,3次関数のグラフの例ですが,極大値が大きいので,y軸方向のスケールを変えています。
drawaxis([-3,9],[-3,10],0.4);
plot((x^3-12*x^2+36*x)/4);
drawtext([1,9],"$f(x)=x^3-12x^2+36x$",size->20);
draw([0,8],[2,8],dashtype->3);
draw([2,0],[2,8],dashtype->3);
drawtext([-0.7,7.8],32,size->16);
drawtext([1.9,-0.7],2,size->16);
drawtext([5.8,-0.7],6,size->16);
drawtext([-0.7,-0.7],"O",size->16);
方眼
方眼は,座標軸以外とのところに点線を引けばよいのです。これを関数化することもできますが,簡単に引けるのですから,むしろそうしない方が自由に描けます。
まず,縦の線。たとえば
forall(-5..5,
if(#!=0,draw([#,-3.5],[#,5.5],dashtype->3,color->[0,0,0]));
);
同様に横の線も引けます。
直線 ax+by+c=0 を描く
y=mx+n ではなく,ax+by+c=0 を描きます。
もちろん,y=mx+n に変形すれば描けるわけですが,この変形を手計算でやらず描こうというわけです。
なんらかの条件下で(たとえば,2点を通る直線)直線の方程式 ax+by+c=0 が得られたとき,その係数を与えるだけで直線が描かれれば便利です。
定義域も指定できるようにしておきましょう。線の太さや描画色まで入れるとかえって面倒なので,必要があれば追加することにすればよいでしょう。
// 直線 ax+by+c=0 をrangeの範囲で描く coeflist=[a,b,c]
drawline(coeflist,range):=(
regional(a,b,c);
a=coeflist_1;
b=coeflist_2;
c=coeflist_3;
if(b==0,
draw(join([-c/a,0],[-c/a,1]));
,
plot(-a/b*#-c/b,start->range_1,stop->range_2);
);
);
軌跡
高校数学には,サイクロイド,外サイクロイド,内サイクロイドなどの曲線を媒介変数で表す例題や問題があります。「曲線の事典」(磯田正美他著 共立出版)には,作図器を用いて描くいろいろな曲線が載っていますが,これらも媒介変数で表すことができればCindyscriptで表示できます。
ここでは,それを,作図器で描くように,媒介変数 t の値を変化させながら描いていく方法を紹介します。
原理は簡単で,内部時計を利用して t の値を時刻として取得し,0からtまでを定義域として表示すればよいのです。
アステロイドの例を,「GeoGebraとCinderella」のページに掲載しましたので,こちらを参照してください。
ベジェ曲線
ベジェ曲線は,2つの端点と制御点を指定して,端点を結ぶ曲線を描くものです。「制御点」は曲がり具合をコントロールします。
制御点が1つの場合は2次関数,2つの場合は3次関数,3つの場合は4次関数・・ の曲線になります。
たとえば,3次の場合,端点が pa,pd,制御点が pb,pc とすると,曲線の式は
(1-t)^3*pa+3*t*(1-t)^2*pb+3*t^2*(1-t)*pc+t^3*pd ( 0≦t≦1 )
です。pa,pb,pc,pd の係数は,二項係数 nCr t^(1-r) (1-t)^r になっています。
引数の数に応じて,2次または3次のベジェ曲線にすると次のようになります。
bezier(list):=(
pa=list_1;
pb=list_2;
pc=list_3;
if(length(list)==3,
plot((1-t)^2*pa+2*t*(1-t)*pb+t^2*pc,stop->1,steps->100);
);
if(length(list)==4,
pd=list_4;
plot((1-t)^3*pa+3*t*(1-t)^2*pb+3*t^2*(1-t)*pc+t^3*pd,stop->1,steps->100);
);
);
さらに,4次のベジェ曲線も追加することもできるでしょう。
プロットデータとその利用
プロットデータとは
コンピュータで曲線を描くとき,実は細かい幅の折れ線を描いています。ちょうど,中学校の数学で2次関数の描くとき,点をいくつかとって結ぶようなものです。この点を「プロットデータ」と呼びましょう。
Cindyscriptで曲線を描くには,それが媒介変数で表現できれば plot([x(t),y(t)])で描くことができます。このときオプションに steps というのがあるのですが,これがプロットする個数を指定するものなのです。
プロットデータで曲線を描く
具体例として,円を描いてみましょう。半径 r の円は,媒介変数表示で x=r cosθ,y=r sinθ と表すことができます。θは角で0から2πで一周します。
したがって, 半径が2であれば plot(2*[cos(t),sin(t)])で円が描かれるのですが,plot() を使わずにプロットデータを作って,それを結んで描いてみましょう。
res=6;
plotdata=apply(0..res,2*[cos(2*pi/res*#),sin(2*pi/res*#);
connect(plotdata);
とすると,plotdata は res=6 なので 0..6 で 0から 6 まで7つの点のリストになります。
実際,println(plotdata) とすると
[[2,0],[1,1.7321],[-1,1.7321],[-2,0],[-1,-1.7321],[1,-1.7321],[2,0]]
がコンソールに表示されます。
connect(plotdata) でこの点のリストを折れ線でつないだ図形を描くと,正六角形が描かれます。(次図左)
次に,res=72 とすると,正七十二角形が描かれるのですが,ほとんど円に見えます。(次図右)
閉曲線の中を塗る
プロットデータを作って閉曲線を描くには drawpoly(プロットデータ) を使います。fill(プロットデータ) を使うと中を塗った閉曲線が描かれます。色は color オプションで指定します。
たとえば,中を塗った楕円を描いてみましょう。
楕円の媒介変数表示は x=acosθ,y=bsinθです。したがって,次のようにすれば楕円が描けます。
res=72;
plotdata=apply(0..res,[3*cos(2*pi/res*#),2*sin(2*pi/res*#)]);
fillpoly(plotdata,color->[1,1,0]);
曲線の長さ
曲線の長さは,数式処理ソフトであれば積分で求めますが,Cinderellaは数式処理はできませんので,数値計算で求めます。
線分の長さは簡単に求められるので折れ線として長さを求めれば近似値が得られます。
半径2の円の場合で計算して確かめてみましょう
まず,正七十二角形として計算します。
len=0;
forall(1..res,len=len+|plotdata_#,plotdata_(#+1)|);
println(len);
とすると,コンソールに 12.5624 と表示されます。
実際の円周の長さは
println(2*2*pi)
とすると 12.5664 と表示されますので,けっこう近い値です。
res=256;
と細かくすると 12.5661 となってさらに近い値になります。