Widget Demo 解体新書

Widget Demo 解体新書:簡単な2次元プロット

2次元グラフを描画してそのプロット点をドラッグできるようにします(なんでデータを改変するのかこのデモの意図がよくわかりませんが...).ドラッグできたりすると複雑そうですが内容はそんなに多くありません.
[2002/01/24]

ウィジェットの準備

まずは,キャンバスを作成しパックします.
canvas .c -relief raised -width 450 -height 300
pack .c
次にキャンバスに座標軸を描きます.原点はキャンバス上の (100,250) として,ここから右と上向きに座標を取ります.描画領域の横幅は 300,高さは 200 です.
.c create line 100 250 400 250 -width 2
.c create line 100 250 100 50 -width 2
.c create text 225 20 -text "2次元プロットの例" -fill brown
続いて,座標軸上の目盛です.X軸では y=245 の高さに 30 おきに 10 個,アンカーを n としてテキストを作成します.Y 軸では x=96 の位置に 40 おきに 5 個,アンカーを e としてテキストを作成します.目盛の線もそれに合わせて作成しています.
for {set i 0} {$i<=10} {incr i} {
  set x [expr {100+($i*30)}]
  .c create line $x 250 $x 245 -width 2
  .c create text $x 254 -text [expr 10*$i] -anchor n
}

for {set i 0} {$i<=5} {incr i} {
  set y [expr (250-($i*40))]
  .c create line 100 $y 105 $y -width 2
  .c create text 96 $y -text [expr $i*50] -anchor e
}

そして,この座標上に点をプロットしていくわけですが, プロット点の座標はあらかじめ決められているものを使います.{x,y} の組になったリストで指定し,これを foreach で一つずつ取り出して点をプロットしていきます.デモでは7点ありますが,ここでは減らして4点とします.ここでも原点をキャンバス上の (100,250) として描画座標を計算している点に注意して下さい(座標の向きにも注意).

foreach point {{12 56} {33 98} {61 180} {98 223}} {
  set x [expr (100+(3*[lindex $point 0]))]
  set y [expr (250-(4*[lindex $point 1])/5)]
  set item [.c create oval [expr $x-6] [expr $y-6] [expr $x+6] [expr $y+6] \
    -fill SkyBlue2]
  .c addtag point withtag $item
}
プロットした点すべてに point のタグを与えています.イベントを設定するときにこのアイテムタグを利用します.プロット点の大きさは 12 (半径6) としています.

アイテムへのバインド設定

残りはキャンバス上のアイテムに対してバインドを与えます.バインドを指定する対象は点のプロット時に与えた point タグを与えたアイテムに対してです.また,現在のアイテムには current というタグが割り付けられているので,バインド時にもこれを使います. イベントに対して割り振るのは次の処理です.ボタン1を押したときにアイテムに selected のタグを付けて,ポインタ移動に対応させてそのアイテムを移動させます.
イベントイベント名処理
アイテム上にポインタが移動Any-Enter タグ current アイテムの色を赤くする
アイテム外にポインタが移動Any-Leave タグ current アイテムの色を水色に戻す
アイテム上でボタン1を押下1 ボタンを押下したアイテムに対して selected タグを付ける
ボタン1を離すButtonRelease-1 selected のタグを外す
ボタン1を押したままポインタが移動B1-Motion タグ selected のアイテムを現在のポインタ位置に動かす
最後のバインドだけアイテムに対してではなく,キャンバス上のイベントに対するものとなっています.

これを行なう実際のコードは次のようになります.アイテムに対して selected のタグを割り付けるのとポインタ移動に伴ってアイテムを動かすのは,それぞれ plotDown と plotMove の関数を指定します.これらは引続き作成していきます.

.c bind point <Any-Enter> ".c itemconfig current -fill red"
.c bind point <Any-Leave> ".c itemconfig current -fill SkyBlue2"
.c bind point <1> "plotDown .c %x %y"
.c bind point <ButtonRelease-1> ".c dtag selected"
bind .c <B1-Motion> "plotMove .c %x %y"

処理関数の作成

plotDown と plotMove の2つの関数を作成します.plotDown では引数として対象ウィジェットとポインタの押下された X, Y 座標を渡します.内部処理では以前の selected タグを削除し,現在のアイテム current タグが付いたものに対して,selected タグを付け直します.またそのアイテムを raise サブコマンドで重なりの上に移動し,引数の座標を連想配列 plot の lastX, lastY に保存します.この連想配列は global 指定し他の関数から参照できるようにします.
proc plotDown {w x y} {
  global plot
  $w dtag selected
  $w addtag selected withtag current
  $w raise current
  set plot(lastX) $x
  set plot(lastY) $y
}
plotMove では引数は plotDown と同様で,処理は selected タグがついたアイテムを引数である現在座標と lastX, lastY からの差分だけ移動させます.移動した後には現在座標を lastX, lastY として保存します.
proc plotMove {w x y} {
  global plot
  $w move selected [expr $x-$plot(lastX)] [expr $y-$plot(lastY)]
  set plot(lastX) $x
  set plot(lastY) $y
}
以上の処理をイベントに応じて繰り返すというわけです.これだけです.簡単でしょ?

これを実行すると次のようになります(スクリプトソース).