07 TikZとの比較

TeXで図を描くために,TikZ がよく使われています。TikZを使えば,簡単な図から,カラーにグラデーションをかけたりしたような図まで描くことができます。TeXample.net というページ(http://www.texample.net/tikz/examples/)にはたくさんの作図例が載っています。

しかし,TikZで図を描くのは必ずしも簡単ではなく,敷居が高いと感じませんか。

この節では,簡単な図を描く場合についてTikZとKeTCindyの比較をしてみます。

簡単な図での違いは

・TikZでは,座標などをあらかじめ計算して数値でコマンドを書く。

・図の細かい調整が必要になったとき,数値を全部変える。

のに対し,KeTCindyでは

・Cinderellaの作図をできるだけ利用すれば,数値を計算しなくてよい。

・細かい調整はCinderella上で行えば数値をいちいち変える必要はない

ということに尽きます。しかし,これだけのことで,作図の能率が格段に違うのです。

もちろん,KeTCindyでも,内容によってはあらかじめ計算をしておく必要のあるものもありますが,全体的なコードの分量が上記の理由によって少なくなるのがKeTCindyのメリットです。

以下にいくつか例を示しましょう。

重心と垂心

高校レベルの作図ですが,KeTCindyの長所がいかんなく発揮される例です。

作図例は,TeXwiki の TikZページにあります。「計算による座標設定」の項です。

https://texwiki.texjp.org/?TikZ#x910ebfe

このような図です。(方眼は非表示にしています)

TikZでは,まず,頂点A,B,Cを \coordinate コマンドで設定します。

次に重心の図では各中点の座標,垂心では垂線の足の座標の計算式を書いています。

・・・ という手順で,重心だけで28行のコードを書いています。

これに対し,KeTCindyの場合,作図はCinderellaで済ませてしまいます。

左側の重心の図では中線は2本だけです。また,点の名前はでき上がり図とは違っています。Cinderellaでの作図時点では点の名前を考慮する必要はありません。

この図を見ながらコードを書きます。

Texparent="fig";

Ketinit();

Addax(0);

Fontsize("La");

Ptsize(4);

// 重心

Listplot([A,B,C,A]);

Listplot([A,E],["Color=[0,1,0]"]);

Listplot([B,F],["Color=[1,0,0]"]);

Listplot([C,D],["Color=[0,0,1]"]);

Drwpt([A,B,C,D,E,F,G]);

Letter([A,"n2e2","B",B,"s2","O",C,"s2","A",E,"s2","C",F,"n2e2","D",D,"n2w2","E"]);

Expr([(A+D)/2,"c","\circ",(B+D)/2,"c","\circ",(A+F)/2,"c","\times",(F+C)/2,"c","\times"]);

Letter([G,"n2","重心"]);

Bowdata([B,E]);

Bowdata([E,C]);

//垂心

Listplot([H,K,L,H]);

Listplot([H,N],["Color=[0,1,0]"]);

Listplot([K,O],["Color=[1,0,0]"]);

Listplot([L,P],["Color=[0,0,1]"]);

Drwpt([H,K,L,N,O,P,M]);

Letter([H,"n2e2","B",K,"s2","O",L,"s2","A",N,"s2","C",O,"n2e2","D",P,"n2w2","E"]);

Letter([M,"s2w2","垂心"]);

Paramark([M,P,K]);

Paramark([L,N,M]);

Paramark([H,O,K]);

Figpdf();

Windispg();

TikZの場合の半分以下の行数で,1行の量もずっと少ないのがお分かりでしょう。

当然作図にかかる時間もずっと少なくて済みます。

でき上がり図です。

次に,この三角形の向きを変えるのですが,頂点の座標を設定し直す必要はありません。

単に,Cinderellaの画面上で頂点をドラッグするだけです。コードの変更はいっさいありません。

ただし,「垂心」の文字の位置を変えた方がよければ,その部分だけ書き換えます。方向の指示 "s2w2"を

"e3" とでもすればよいでしょう。

外接円と内接円

これも,TeXwiki の TikZページにある例です。

外心は各辺の垂直二等分線の交点で作図できますが,TikZでは円を描くためにその半径を2点間の距離を計算して求めます。

内心は各角の二等分線の交点ですが,二等分線を引くために各辺上に一定の長さの点を設定するという作業が必要です。ひし形を作ることで二等分線を描くためです。また,内心円の半径についても計算する必要があります。

かくして次の図ができます。コードの量はプリアンブルなどを除いても30数行になります。

KeTCindyでは,作図はCinderellaで済ませてしまいます。

さらに,垂直二等分線や角の二等分線をKeTCindyで表示するために,それぞれの直線上に点をとっておきます。

これで準備は完了。図を見ながらコードを書きます。全部で26行ありますが,全体の量からすればTikZ よりずっと少ないのがわかるでしょう。インタラクティブに頂点の位置を変更できるのはもちろんのことです。

Texparent="fig";

Ketinit();

Addax(0);

Fontsize("La");

Ptsize(4);

// 外接円

Listplot([A,B,C,A]);

Circledata([G,A]);

Listplot([O,P],["Color=[0,1,0]"]);

Listplot([S,T],["Color=[1,0,0]"]);

Listplot([Q,R],["Color=[0,0,1]"]);

Drwpt([A,B,C,G]);

Letter([A,"n2e2","B",B,"s2","O",C,"s2","A"]);

Letter([G,"w3","外心"]);

Paramark([G,D,C]);

//内接円

Listplot([H,K,L,H]);

Circledata([M,N]);

Listplot([L,U],["Color=[0,1,0]"]);

Listplot([K,V],["Color=[1,0,0]"]);

Listplot([H,W],["Color=[0,0,1]"]);

Drwpt([H,K,L,M]);

Letter([H,"n2e2","B",K,"s2","O",L,"s2","A"]);

Letter([M,"w3","内心"]);

Figpdf();

Windispg();

繰り返し処理

TeXwiki の TikZページ(前項と同じ)に,繰り返し処理 \foreach を用いる例として,時計が載っています。

繰り返し処理をするとプログラミングらしくなりますが,Cindyscriptでも repeat() で繰り返し処理ができます。

この例の場合は,KeTCindyでは三角関数で目盛の位置を求めるので,TikZとあまりかわらないコード量になっています。Cinderellaの作図では,時計の針を描くために線分を二つ引き,線分上に針の終点にあたる点を作図しています。

Texparent="fig";

Ketinit();

Addax(0);

Fontsize("La");

Ptsize(4);

Circledata([A,B],["dr,3"]);

repeat(12,s,

if(mod(s,3)==0,

Listplot(text(s),[2.6*[cos(pi/2-s*pi/6),sin(pi/2-s*pi/6)],3*[cos(pi/2-s*pi/6),sin(pi/2-s*pi/6)]],["dr,2"]);

,

Listplot(text(s),[2.7*[cos(pi/2-s*pi/6),sin(pi/2-s*pi/6)],3*[cos(pi/2-s*pi/6),sin(pi/2-s*pi/6)]]);

);

Letter([2.3*[cos(pi/2-s*pi/6),sin(pi/2-s*pi/6)],"c",text(s)]);

);

Listplot([E,A,F],["dr,6"]);

Figpdf();

Windispg();

次の図は,左が TikZのもの,右が上のスクリプトで描いたKeTCindyのものです。


ボロミアンリング

九州大学付属図書館のサイトで紹介されている例です。

該当ページはこちら https://guides.lib.kyushu-u.ac.jp/c.php?g=774891&p=5559261 です。

KeTCindyでのコードは次のようになります。

Texparent="borro";

Ketinit();

Addax(0);

Circledata("1",[A,D],["Rng=[pi/4+0.1,7*pi/4-0.1]"]);

Circledata("2",[A,D],["Rng=[-pi/4+0.1,pi/4-0.1]"]);

Arrowhead(D,"cr1",[2]);

Circledata("3",[B,E],["Rng=[pi/2-0.2,pi-0.3]"]);

Circledata("4",[B,E],["Rng=[-pi-0.2,pi/2-0.3]"]);

Arrowhead(E,"cr4",[2]);

Circledata("5",[C,F],["Rng=[-pi/2+0.3,pi+0.2]"]);

Circledata("6",[C,F],["Rng=[pi+0.3,3*pi/2+0.1]"]);

Arrowhead(F,"cr5",[2]);

Fontsize("La");

Expr([D,"w3","K_1",E,"e3","K_2",F,"n3e3","K_3"]);

Figpdf();

Windispg();

コードの分量もそうですが,使っている数字の数を比べてみましょう。

Tikzでは,円の中心の座標や点の位置は数値で表すのに対し,KeTCindyでは,点の名称です。

描き方を示しましょう。

(1) 3つの円は同じ半径にするので,「固定した半径の円を描く」ツールを使います。

適当な位置に3つの円A,B,Cを作図します。

(2) 矢じりを描く位置に点をとります。点D,E,Fです。

(3) 図を見ながらスクリプトを書きます。

まず,円Aについて書きましょう。

白抜きの点が2つあるので,円を二つに分割して描きます。

Circledata("1",[A,D],["Rng=[pi/4+0.1,7*pi/4-0.1]"]);

Circledata("2",[A,D],["Rng=[-pi/4+0.1,pi/4-0.1]"]);

Arrowhead(D,"cr1",[2]);

矢じりの位置を表す点Dを利用して円を描いています。

白抜きの位置は,およその見当をつけて Rng で定義域を決めるわけです。

加減している 0.1 は実は適当に決めたものです。

同様にして,B,Cの円について描きます。(先頭のスクリプトをごらんください)

定義域は適当に見当をつけ,描いてみてから調整しています。計算はしていません。

ここが肝心で,KeTCindyでは試行錯誤で位置を決めるという方法が成立するのです。

もちろん,厳密に書くのであれば計算して定義域を指定すればよいのです。

上の図もよく見るとC の位置がすこしずれていますね。

このように適当に作図したものでも,PDFにすると次のようになります。

それらしくなったでしょう。白抜きの具合が気に入らなければ,適当に値を増減しましょう。

ボロミアンリング2

TeXample.net (http://www.texample.net/tikz/examples/borromean-rings/)にある例です。この図では線図ではなく幅を持つリングで色塗りをしています。

「Tex」のリンクでソースを見ることができます。一見するとかなりの量ですが,説明がほとんどなので,実際のコード量はたいしたことありません。ただし,描画コマンドの前に,いくつかコマンドを定義していますし,円の重なり部分を処理する \crip を使っていて,そう簡単ではなさそうです。

上の図はKeTCindyで描いたものですが,KeTCindyでもちょっとした知識が必要です。それは「プロットデータ」と「閉曲線の作り方」です。

まず,円の中心を3つ A,B,C として作図しておきます。(後から作る点と区別するためにインスペクタで色を緑にしています。)

この点を中心に半径4と半径3の円を描きます。(半径はこれに限りません)

Circledata("11",[A,A+[r1,0]]);

Circledata("12",[A,A+[r2,0]]);

Circledata("21",[B,B+[r1,0]]);

Circledata("22",[B,B+[r2,0]]);

Circledata("31",[C,C+[r1,0]]);

Circledata("32",[C,C+[r2,0]]);

これをもとに,リングのそれぞれの部分を閉曲線として定義して色塗りをするのです。

閉曲線の定義に必要なのは,開始点で,円の交点からスタートして一周するようにします。

ここで,どの点から始めて一周するか,という設計をあらかじめしておく必要があります。

まず,赤の左上の部分を作りましょう。

Putintersect("D","cr11","cr21",1);

とすると,図の交点がDとして作られます。cr11はAを中心とする外側の円,cr21はBを中心とする外側の円のプロットデータで,交点は2つできるので,1番目の方です。2つの交点の順番は,円が3時の方向から反時計回りに描かれていくので1番目は右上の点となります。

(プロットデータについては「プロットデータについて」の説明をごらんください)

Dから出発して,反時計回りに dr11 をたどり,c21 との交点にきたら c21を逆(時計回り)にたどり,c12との交点にきたら c12を逆にたどり,c21との交点でc21を逆にたどるとDに戻ります。

逆向きに回ると

DからC21,C12との交点でC12,C21との交点でC21,C12との交点でC11を逆にたどってD。

となります。

反時計回りに進むときは,円のプロットデータ c11 がそのまま使えます。時計回りに進むときはプロットデータを逆向きに進むので Invert() 関数を用いてプロットデータを逆向きにします。

そこで,後者の道筋を使う方が「逆向き」が少ないので,次のようにして閉曲線を作ります。

Enclosing("1",["cr21","cr12","cr21","Invert(cr11)"],[D]);

"1" は閉曲線の番号,次のリストが道筋,最後が出発点です。「の交点で」は指定不要です。

これで,"en1"という閉曲線のプロットデータができるので,shade() で色塗りをします。

Shade(["en1"],["Color=red"]);

コードを再掲しましょう。次の3行でひとつ描かれます。

Putintersect("D","cr11","cr21",1);

Enclosing("1",["cr21","cr12","cr21","Invert(cr11)"],[D]);

Shade(["en1"],["Color=red"]);

同じようにして残りの部分を塗っていけばよいのですが・・・・ 実はそう簡単ではありません。

赤のもう一つの部分ですが,左下に交点Eをとると次のようになります。(とる点Eは右上でもかまいません)

Putintersect("E","cr11","cr22",1);

このEから,反時計回りが多いように出発するのですが,

Eから c11,c22 となって次の交点が,円の中心の3時の方向より少し上にあります。

点Aを原点に置いたとすると,交点がx軸より少し上にあるので,次に下に進むとx軸をまたぐことになります。(c22を進むときは中心がBの円なので「またぐ」ことは起りません)

円のプロットデータは3時の方向(原点を中心としたときx軸の座標(r,0))から出発して一周します。

x軸をまたぐということはプロットデータの終点から始点に行くことになります。時計回りに回るときは,始点から逆向きに終点に飛ぶわけです。このような「ワープ」はできないので,次のようにします。

現在の点から始点まで戻るプロットデータと,終点から戻るプロットデータをつなぐ

Enclosing() では,指定された曲線をたどっていくとき,交点は計算して経路を変更してくれます。したがって,x軸をまたぐときは(プロットデータの端から端へワープするときは)同じデータを続けて書くことで上の作業をしてくれます。

かくして,次のようにして閉曲線を作ればうまくいきます。

Enclosing("2",["cr11","cr22","Invert(cr12)","Invert(cr12)","cr22"],[E]);

なお,同心円の幅が変わると,x軸をまたぐ,またがない か変わるので注意が必要です。

このようにしてひとうひとつ作っていきます。1つ要領がわかれば,図を見ながら,スクリプトはコピーして書き直していけばよいでしょう。

最後に,はじめに描いた同心円を消します。

Circledata("11",[A,A+[r1,0]],["nodisp"]);

と,"nodisp" オプションをつけます。

スクリプトの全体は次のようになります。(前後の基本設定は略してあります)

r1=4; // 外側の円の半径

r2=3; // 内側の円の半径

Circledata("11",[A,A+[r1,0]],["nodisp"]);

Circledata("12",[A,A+[r2,0]],["nodisp"]);

Circledata("21",[B,B+[r1,0]],["nodisp"]);

Circledata("22",[B,B+[r2,0]],["nodisp"]);

Circledata("31",[C,C+[r1,0]],["nodisp"]);

Circledata("32",[C,C+[r2,0]],["nodisp"]);

Putintersect("D","cr11","cr21",1);

Putintersect("E","cr11","cr22",1);

Putintersect("F","cr21","cr32",2);

Putintersect("G","cr21","cr31",2);

Putintersect("H","cr31","cr11",1);

Putintersect("K","cr31","cr12",2);

Enclosing("1",["cr21","cr12","cr21","Invert(cr11)"],[D]);

Shade(["en1"],["Color=red"]);

Enclosing("2",["cr11","cr22","Invert(cr12)","Invert(cr12)","cr22"],[E]);

Shade(["en2"],["Color=red"]);

Enclosing("3",["cr32","Invert(cr22)","cr32","cr21"],[F]);

Shade(["en3"],["Color=green"]);

Enclosing("4",["cr31","cr22","cr22","cr31","Invert(cr21)","Invert(cr21)"],[G]);

Shade(["en4"],["Color=green"]);

Enclosing("5",["cr11","cr32","cr11","Invert(cr31)"],[H]);

Shade(["en5"],["Color=blue"]);

Enclosing("6",["cr12","Invert(cr32)","Invert(cr32)","cr12","cr31"],[K]);

Shade(["en6"],["Color=blue"]);

有向グラフ

Tasuku Soma 氏のページ https://www.opt.mist.i.u-tokyo.ac.jp/~tasuku/tikz.html にある例です。

TikZで点をとる \node はなかなか便利で,点の形状や塗り色を指定することができます。

\node でとった点(ここでは円の形状)には大きさがあり, \draw[->] で矢線を引くと,うまいぐあいに円の縁から縁まで矢線が引かれます。

これとおなじことをKeTCindyでやってみましょう。

まずCinderellaの作図機能でノードとなる点をとります。

これらの点を中心に円を描きます。

ひとつひとつ Circledata() で描いてもよいのですが(1つ描いたらあとはコピーして点の名前を変えるだけ)

点のリストを作っておいて繰り返しのrepeat() でまとめて描きます。

円の半径は変数 r に代入します。

plist=[A.xy,B.xy,C.xy,D.xy,E.xy,F.xy];

r=0.5;

repeat(6,s,

Circledata(text(s),[plist_s,plist_s+[r,0]]);

);

色塗りをするには Shade() を使います。

Cirlcedata() で作ったプロットデータは cr1〜cr6 なので,これもリストにしてまとめて色塗りします。

こんどはrepeat()ではなく forall()を使います。リストのすべての要素に対してある処理をする関数です。

clist=["cr1","cr2","cr3","cr4","cr5","cr6"];

forall(clist,Shade([#],["Color=cyan"]));

次は矢線です。これも,矢線の始点、終点をリストにしておいて,Arrowdata()で矢線を描きます。円の半径分を,Cutendオプションで短くします。

arrowlist=[[B,A],[B,C],[A,C],[A,F],[C,F],[C,D],[F,D],[F,E],[D,E]];

repeat(9,s,Arrowdata(text(s),arrowlist_s,["Cutend="+text(r)]));

ノードに文字を入れます。

Letter([A,"c","a",B,"c","s",C,"c","b",D,"c","d",E,"c","t",F,"c","c"]);

これででき上がり。

前後の設定も含めて,全部のスクリプトを示すと次のようになります。

Ketinit();

Setfiles("dgraph");

Addax(0);

Fontsize("h");

Ptsize(4);

plist=[A.xy,B.xy,C.xy,D.xy,E.xy,F.xy];

r=0.5;

repeat(6,s,

Circledata(text(s),[plist_s,plist_s+[r,0]]);

);

clist=["cr1","cr2","cr3","cr4","cr5","cr6"];

forall(clist,Shade([#],["Color=cyan"]));

arrowlist=[[B,A],[B,C],[A,C],[A,F],[C,F],[C,D],[F,D],[F,E],[D,E]];

repeat(9,s,Arrowdata(text(s),arrowlist_s,["Cutend="+text(r)]));

Letter([A,"c","a",B,"c","s",C,"c","b",D,"c","d",E,"c","t",F,"c","c"]);

Windispg();

TikZのコードより少し多いですが,考え方は同じです。

円の大きさは,変数 r に代入しているので,この値を変えれば円のサイズが変わるのと同時に,矢線の位置も自動的に変わります。

完全グラフ

Jean-Noël Quintin 氏のページ http://www.texample.net/tikz/examples/complete-graph/ にある例です。これはモノクロですが,カラーでやってみました。

この完全グラフはCindyscriptのマニュアルにも載っています。それを使ってKeTCindyで少し手直しをするだけです。

n=17;

f(x):=4*[sin(x),cos(x)];

steps=2*pi*(1..n)/n;

pts=apply(steps,f(#));

segs=pairs(pts);

repeat(length(segs),s,Listplot(text(s),segs_s,["Color=blue"]));

Ptsize(6);

Pointdata("1",pts,["Color=green"]);

このように簡単にできるのですが,実は,Cinderella単独でもPDFにできます。

n=17;

f(x):=4*[sin(x),cos(x)];

steps=2*pi*(1..n)/n;

pts=apply(steps,f(#));

segs=pairs(pts);

drawall(segs);

drawall(pts);

これだけで画面に表示できて,これをそのままPDFで書き出します。

これをTeXの文書に挿入すればいいわけです。

< 戻る >