大きさの変化をアニメーションさせる
今までのセクションでは、プログラムで描いたグラフィックスは動きませんでした。 これを動かしてみましょう。 例えば、次のような年毎の犬猫飼育推定数の変化を、図形の大きさを変えることで表します。年 | 2013年 | 2014年 | 2015年 | 2016年 | 2017年 | 2018年 | 2019年 | 2020年 | 2021年 | 2022年 | 2023年 |
---|---|---|---|---|---|---|---|---|---|---|---|
猫 | 8409 | 8425 | 8297 | 8333 | 8672 | 8849 | 8764 | 8628 | 8946 | 8837 | 9069 |
犬 | 8714 | 8200 | 7994 | 8008 | 7682 | 7616 | 7579 | 7341 | 7106 | 7053 | 6844 |
図形を描く位置やデータを切り替えるタイミング(アニメーションさせるスピード)は、 Datamateライブラリ が提供する機能を使います。個別にその処理を書かずに、Datamateに任せることができます。
年ごとに円の大きさを切り変える
上の表の年毎に変化する犬猫の飼育推定数を、円の大きさで示したのがリストT2-2-1で、下はその実行結果です。犬の方が多かったのが逆転し、猫が増えている様子が表されているでしょうか。順を追って、プログラムを理解していきましょう。
クリックで開始⇔停止
[1]視覚化の対象となる内部テーブルを作る
元になるデータは、ヘッダ(タイトル行)を除くと、2行のテーブルで、11年分の列があります。Datamatemのmake()関数を使って、1行ごとに、行の名前とデータを指定します。
Datamate.make('行の名前', [データの配列]);タイトル行を含め3行のテーブルなので、次のようにmake()関数を指定します。
Datamate.make("年", ["2013年", "2014年", "2015年", "2016年", "2017年", "2018年", "2019年", "2020年", "2021年", "2022年", "2023年"]); Datamate.make("猫", [8409, 8425, 8297, 8333, 8672, 8849, 8764, 8628, 8946, 8837, 9069]); Datamate.make("犬", [8714, 8200, 7994, 8008, 7682, 7616, 7579, 7341, 7106, 7053, 6844]);
[2]データを表示する領域(レイアウトスペース)を作る
表示のための領域をレイアウトスペースを Datamate.makeArea()関数を使って作り、これを縦に2つ(1行2列)に分割します。Datamate.makeAreas(x座標, y座標, 幅, 高さ, 横方向のエリア数, 縦方向のエリア数);キャンバス全体をレイアウトスペースとするため、(0, 0)を左上角とし、幅はwidth、高さはheightと指定します。
Datamate.makeArea(0, 0, width, height, 2, 1);
[3]分割したエリアに番号を振る
エリアの番号はデフォルトにまま、左を0、右を1とし、 最初のデータ行(猫のデータ)を左に、2つめのデータ行(犬データ)を右に表示することにします。 明示的にエリアに番号を振る場合は、Datamate.bindAreas()関数を使います。Datamate.bindAreas([番号, 番号,..]);しかし、この例では、デフォルトのままなので、bindAreas()関数の実行は不要です。 次の指定をしたのと同じことです。
Datamate.bindAreas([0, 1]);
[4]テーブルから値を取り出す
データを反映したグラフィックスをエリアに描画するために、テーブルから値を取り出すにはvalue()関数を使います。Datamate.value(行の指定, 列の指定); 行の指定:行のタイトルあるいはデータテーブルのインデックス(0から振られる。ヘッダ行の次が0) 列の指定:列のタイトルあるいはデータテーブルのインデックス(0から振られる。ヘッダ列の次が0)例えば、猫の2013年のデータを取り出すには、次のように書きます。
Datamate.value('猫', 0); または Datamate.value(Datamate.rowName(0), 0);rowName()関数は行のヘッダ(名前)を返す関数です。列のヘッダはcolumnName()関数で得られます。
[5]エリアの描画をする関数を定義する
データを円の大きさとして描画するこのプログラムの描画処理を分解すると、次のようになります。- ・テーブル内のデータを取り出す
- ・そのデータから表示する円の直径を計算する
- ・色を指定し、表示エリアの中央に、円を描く
- ・色を指定し、表示エリアの中央に、データ名と数値を描く
関数内での描画処理には、2つの情報が必要です。
- ・猫、犬どちらのデータを取り出すか(行のタイトル)
- ・円を描く表示エリアの番号(猫が0、犬が1)
function drawData(syurui, number) { ..... }例えば、猫のデータを左側のエリアに表示するには、次のように引数を指定します。
drawData('猫', 0); // 表示エリア0に'猫'データを表示 または drawData(Datamate.rowName(0), 0);
[6]エリアの中央の位置、大きさなどの情報を得る
描画処理を行う関数(上の[5]で定義したdrawData()関数)内で、 描画をする際には、エリアの情報(中央位置や幅、高さ)が必要になります。 Datamate.area()関数は、描画エリアの情報(位置や大きさの情報を保持するareaオブジェクト)を返します。Datamate.area(エリアに振った番号);この例ではdrawData()関数内で、引数で渡されたエリア番号を使って、次のように領域情報を得ます。
const area = Datamate.area(number);Datamate.area()関数が返す値(areaオブジェクト)には、次のような変数に領域の情報が保存されています。
- ・左上隅の座標: x, y
- ・中央の座標: centerX, centerY
- ・左辺,右辺のx座標: left, right
- ・上辺,下辺のy座標: top, bottom
- ・幅と高さ: width, height
circle(area.centerX, area.centerY, w); //wは直径
[7]データを順に表示する(再生する)
テーブルのデータを順に表示することで、データの変化をアニメーションとして描画できます。 この例だと、横方向(行)に注目するデータを変えることで、時系列(年ごと)変化を表示できます。Datamateには、今注目している(focusを当てている)データのインデックスを返す関数foucsX()とfoucsY()があります。引数に0を指定すると今フォーカスしている値のインデックスを返します。引数に1を指定するとフォーカスしている値の次の値のインデックスを返します(つまり、引数はフォーカスしている値からの距離を意味する)。
let indexX = Datamate.foucsX(0); //フォーカス中のデータの横方向(列)のインデックスを返す let indexY = Datamate.foucsY(0); //フォーカス中のデータの縦方向(行)のインデックスを返すそして、play()関数を使って、フォーカスするデータを順に動かします(これを再生と呼びます)。再生することで、関数foucsX()とfoucsY()が返すインデックスが順に変化します。 play()の引数には、1秒間に何回データを動かす(次のデータにフォーカスを移す)のかを指定します。
Datamate.play(1秒間に横方向に動かす回数, 1秒間に縦方向に動かす回数);例えば、引数に2を指定すると0.5秒ごとに、引数に0.4を指定すると2.5秒ごとに、foucsX()関数、foucsY()関数で取り出されるデータのインデックスが順番に進みます。 play()関数の引数に0を指定するとデータは動きません。
再生しない(Datamate.play()関数を実行しない)場合、foucsX()、foucsY()関数で取り出されるデータのインデックスは先頭のままです。
再生をループしたい時は、loop()関数を使います。
Datamate.loop(横方向の再生をループするか, 縦方向の再生をループするか);引数をtrueとすると再生がテーブルの最後の値までいくと先頭に戻ります。デフォルト(指定しない場合)はfalseで、最後のインデックスで止まります。
playの機能を使うと、プログラムの意図にあったスピードでアニメーションさせることができます。
【リストT2-2-1】 let title = "猫と犬の飼育推計数"; let titleX = 120; //この↑表題を表示するx座標 function setup() { createCanvas(450, 250); Datamate.make("年", ["2013年", "2014年", "2015年", "2016年", "2017年", "2018年", "2019年", "2020年", "2021年", "2022年", "2023年"]); Datamate.make("猫", [8409, 8425, 8297, 8333, 8672, 8849, 8764, 8628, 8946, 8837, 9069]); Datamate.make("犬", [8714, 8200, 7994, 8008, 7682, 7616, 7579, 7341, 7106, 7053, 6844]); Datamate.makeAreas(0, 0, width, height, 2, 1); Datamate.play(0.3, 0); //横方向に動かす Datamate.loop(true, false); //ループする。 noStroke(); textAlign(CENTER, CENTER); } function draw() { background(210); drawData('猫', 0); // 表示域0に'猫'データを表示 drawData(Datamate.name(0), 0); drawData('犬', 1); // 表示域1に'犬'データを表示 drawData(Datamate.name(1), 1); } function drawData(syurui, number) { let area = Datamate.area(number); //表示エリアをとりだす let dataIndex = Datamate.focusX(); //今フォーカスしているデータのインデックス let year = Datamate.columnName(dataIndex); //列のヘッダ let value = Datamate.value(syurui, dataIndex); let w = map(value, 6000, 10000, 30, area.width); //データを円の直径に換算 fill(200, 0, 0); circle(area.centerX, area.centerY, w); fill(0); text(title+"("+year+")", titleX, 30); //表題と年の表示 fill(255); text(syurui + "\n" + value + "千頭", area.centerX, area.centerY); //データの種類と頭数の表示 }プログラムの最後の行のtext()関数の第1引数は、
syurui + "\n" + value + "千頭"表示する文字列を+演算子で作り出しています。"\n"は改行を表します。猫あるいは犬の文字列を表示した後、改行して、数値を表示するという意味です。
年毎に円の大きさをスムーズに変化させる
リストT2-2-1は表示するデータが変わるタイミングで、新しい円の大きさに「ぱっ」と切り替わります。円の大きさの変化をスムーズにしたのが、下のスケッチです。クリックで開始⇔停止
今フォーカスしている値のインデックスを返すfocusX()、focusY()関数は、今のインデックスと次のインデックスとの間を補間(内挿)する機能を備えています。補間機能を有効にするには、第2引数にtrueを指定します。
let indexHokan = Datamate.focusX(0, true); //focusX(0)とfocusX(1)の間を補間したインデックスこのように書くと、play()関数で指定したスピードで、focusX()が返すインデックスが徐々に動きます(整数インデックスの間の値が返る)。それをvalue()関数に指定すると、連続的に変化する値が得られ、この値を使って、円の大きさを計算すると、スムーズに変化させることができます。
let value = Datamate.value(syurui, indexHokan);ただ、このvalueをそのまま数値の表示に使うと、数が大きく変化して見にくいので、数値表示用のデータは補間しない値を使っています。
text(syurui + "\n" + Datamate.value(syurui, dataIndex) + "千頭", area.centerX, area.centerY); //データの種類と頭数の表示
【リストT2-2-2】 //リストT2-2-1のdrawData()関数を下に変更 function drawData(syurui, number) { let area = Datamate.area(number); //表示エリアをとりだす let dataIndex = Datamate.focusX(); //今フォーカスしているデータのインデックス let indexHokan = Datamate.focusX(0, true); //今フォーカスしているデータと次のデータとの間のインデックス let year = Datamate.columnName(dataIndex); //列のヘッダ let value = Datamate.value(syurui, indexHokan); let w = map(value, 6000, 10000, 30, area.width); //データを円の直径に換算 fill(200, 0, 0); circle(area.centerX, area.centerY, w); fill(0); text(title+"("+year+")", titleX, 30); //表題と年の表示 fill(255); text(syurui + "\n" + Datamate.value(syurui, dataIndex) + "千頭", area.centerX, area.centerY); //データの種類と頭数の表示 }
矩形を成長させる
リストT2-1-1では、矩形の高さでデータを表しました。これに手を加え、矩形の高さをアニメティックに成長させてみましょう。クリックで再度成長
上の犬猫飼育数の例とは違い、0から終着点の寿命の行の値まで動かしたいわけです。
名前 | イカ | マグロ | オオサンショウウオ | ウニ | シロナガス クジラ |
---|---|---|---|---|---|
寿命 | 1.0 | 20.0 | 55.0 | 70.0 | 100.0 |
Datamate.jsの再生、補完機能は、テーブルデータ間の移動なので使えません。そこで、徐々に大きくなる矩形の高さを覚えておく配列をプログラムが持つことにします。
let currentH = []; //項目ごとの矩形の途中の高さ for (let i=0; i<Datamate.columnCount(); i++) { currentH[i] = 0.0; //矩形の高さを最初は0にしておく }そして、この値を変数henkaで指定したピクセル数ずつ大きくして、矩形を描きます。
currentH[index] = currentH[index] + henka; //新しい高さ rect(area.centerX-rw/2, area.bottom-currentH[index], rw, currentH[index]);rect()関数の第1, 2引数は、矩形の左上角のx, y座標です。
【リストT2-2-3】 let max = 250.0; //矩形の高さの最大値 let min = 0.0; //矩形の高さの最小値 let rw = 50; //矩形の幅 let currentH = []; //項目ごとの途中の高さ let henka = 1.0; //矩形が伸びるスピード function setup() { createCanvas(500, 320); noStroke(); textAlign(CENTER, CENTER); //文字を表示する際、中心の座標を指定 Datamate.make("名前", ["イカ", "マグロ", "オオサンショウウオ", "ワニ", "シロナガスクジラ"]); Datamate.make("寿命", [1.0, 20.0, 55.0, 70.0, 100.0]); Datamate.makeAreas(0, 0, width, height-50, Datamate.columnCount(), 1); //Datamate.columnCount()はデータの列数 for (let i=0; i<Datamate.columnCount(); i++) { currentH[i] = 0.0; //矩形の高さを最初は0にしておく } } function draw() { background(240); for (let i=0; i<Datamate.columnCount(); i++) { drawData(i); } } function drawData(index) { let area = Datamate.area(index); let value = Datamate.value("寿命", index); let h = map(value, 0, 100, min, max); if (currentH[index] >= h) { //寿命を表す高さになったら、成長を止め、色を変え、寿命を表示 henka = 0; fill(200, 0, 0); text(value +"年", area.centerX, area.bottom+30); } else { henka = 1; fill(0, 0, 200); } currentH[index] = currentH[index] + henka; //新しい高さ rect(area.centerX-rw/2, area.bottom-currentH[index], rw, currentH[index]); fill(0); text(Datamate.value("名前", index), area.centerX, area.bottom+15); }
演習問題
【問題T2-2-1】リストT2-2-2を変更し、データを円の大きさではなく、画像の大きさで表してみよう。
クリックで開始⇔停止
画像ファイルは、画像ページからコピーできます。