ActiveObjectsでたったの2時間で作れる3次元グラフ

ついカッとなってやった、ActiveObjectsのいいところを見せられれば何でもよかった。

2時間とは

ここから


そろそろActiveObjectsで本気だす!!!
ここまで

canvas3DGraph.js+prototype.js+Wicket+ActiveObjects+MySQLで、3Dグラフ表示&更新プログラムを作った。

画面&データ

左側のブラウザでグラフデータの追加をして、右側のデータで自動的に3次元グラフの描画をしてる。
ちょっとずつデータを追加していく様子の、画面とデータを張り付けてく。

初期データ
mysql> select * from threeDData;
+----+------+------+------+
| id | z    | x    | y    |
+----+------+------+------+
|  1 |   30 |   50 |   40 |
|  2 |   80 |  120 |   70 |
|  3 |  100 |  100 |  100 |
|  4 |  500 |  500 |  500 |
|  5 |  400 |  300 |  400 |
+----+------+------+------+
5 rows in set (0.00 sec)

データ追加1
mysql> select * from threeDData;
+----+------+------+------+
| id | z    | x    | y    |
+----+------+------+------+
|  1 |   30 |   50 |   40 |
|  2 |   80 |  120 |   70 |
|  3 |  100 |  100 |  100 |
|  4 |  500 |  500 |  500 |
|  5 |  400 |  300 |  400 |
|  6 |  100 |  400 |  300 |
+----+------+------+------+
6 rows in set (0.00 sec)

データ追加2
mysql> select * from threeDData;
+----+------+------+------+
| id | z    | x    | y    |
+----+------+------+------+
|  1 |   30 |   50 |   40 |
|  2 |   80 |  120 |   70 |
|  3 |  100 |  100 |  100 |
|  4 |  500 |  500 |  500 |
|  5 |  400 |  300 |  400 |
|  6 |  100 |  400 |  300 |
|  7 |  800 |  800 |  800 |
+----+------+------+------+
7 rows in set (0.00 sec)

データ追加3
mysql> select * from threeDData;
+----+------+------+------+
| id | z    | x    | y    |
+----+------+------+------+
|  1 |   30 |   50 |   40 |
|  2 |   80 |  120 |   70 |
|  3 |  100 |  100 |  100 |
|  4 |  500 |  500 |  500 |
|  5 |  400 |  300 |  400 |
|  6 |  100 |  400 |  300 |
|  7 |  800 |  800 |  800 |
|  8 |  200 |  800 |  300 |
+----+------+------+------+
8 rows in set (0.00 sec)

ソース

データ追加画面 - AddDataPage.html
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:wicket="http://wicket.sourceforge.net/">
  <head>
    <title>データの追加</title>
  </head>
  <body>
    <h1>データの追加</h1>

    <div wicket:id="message">メッセージ</div>

    <form wicket:id="addForm">
      X: <input type="text" wicket:id="x"/><br/>
      Y: <input type="text" wicket:id="y"/><br/>
      Z: <input type="text" wicket:id="z"/><br/>
      <input type="submit" wicket:id="add" value="追加" />
    </form>

  </body>
</html>
データの追加画面 - AddDataPage.java
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.Model;
import net.java.ao.EntityManager;
import java.sql.SQLException;

/**
 * データの追加
 */
public class AddDataPage extends WebPage {

    /**
     * コンストラクタ。
     */
    public AddDataPage() {
        final Label label = new Label("message", new Model("データを追加できます。"));
        this.add(label);

        Form addForm = new Form("addForm");
        final TextField x = new TextField("x", new Model("0.0"));
        final TextField y = new TextField("y", new Model("0.0"));
        final TextField z = new TextField("z", new Model("0.0"));
        addForm.add(x);
        addForm.add(y);
        addForm.add(z);
        addForm.add(new Button("add") {
            public void onSubmit() {
                Double xValue = Double.valueOf((String)x.getModel().getObject());
                Double yValue = Double.valueOf((String)y.getModel().getObject());
                Double zValue = Double.valueOf((String)z.getModel().getObject());

                EntityManager manager = new EntityManager( "jdbc:mysql://localhost/aotest", "root", null );

                java.util.logging.Logger.getLogger("net.java.ao").setLevel(java.util.logging.Level.FINE);

                try {
                        ThreeDData data = manager.create( ThreeDData.class );
                        data.setX( xValue );
                        data.setY( yValue );
                        data.setZ( zValue );
                        data.save();
                } catch ( SQLException e ) {
                        throw new RuntimeException( e );
                }


                setResponsePage( new AddDataPage() );
            }
        });
        this.add(addForm);
    }
}
グラフの画面 - ThreeDGraph.html
<html>
<head>
<title>3次元グラフ</title>
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="excanvas.js"></script>
<script type="text/javascript" src="canvas3DGraph.js"></script>
<script type="text/javascript">
/**
 * 3Dグラフを表示する。
 */
function show3DGraph() {
        // データを取得するために、サーバにリクエストを出す。
        var myAjax = new Ajax.Request(
                'http://192.168.1.21:8080/wicket/',
                {
                        method: 'get',
                        parameters: 'wicket:bookmarkablePage=:ThreeDJson',
                        onComplete: show3DGraph_cb
                }
        );
}

/**
 * 3Dグラフを表示する。(サーバからのコールバック後の処理)
 */
function show3DGraph_cb( req ) {
        // サーバのレスポンスからデータを取得する。
        var json = req.responseText.replace( /<span[^>]*>/, "" ).replace( /<\/span>/, "" ).replace( /\r/, "" ).replace( /\n/, "" );
        var data = eval( json );

        // graphオブジェクトを取得する。
        var g = new canvasGraph( 'graph' );

        // データを並べ替える。
        data.sort( sortNumByZ );

        // グラフを描く。
        g.drawGraph( data );
}

// 画面ロード時に実行する。
Event.observe( window, "load", function(){
        // 3Dグラフの実行を一定時間毎に行うように、タイマーを設定する。
        setInterval( show3DGraph, 1000 );
});
</script>
<style type="text/css">
#g-holder {
        height:620px;
        position:relative;
}

#canvasDiv{
        border:solid 1px #e1e1e1;
        width:600px;
        height:600px;
        position:absolute;
        top:0px; left:0px;
        z-index:10;
}
#x-label{
        position:absolute;
        z-index:2;
        top:340px;
        left:580px;
}

#y-label{
        position:absolute;
        z-index:2;
        top:10px;
        left:220px;
}

#z-label{
        position:absolute;
        z-index:2;
        top:540px;
        left:10px;
}

#gInfo div.gText{
        position:absolute;
        z-index:-1;
        font:normal 10px Arial;
}
</style>
</head>
<body>
&nbsp;
<div id="g-holder">
        <div id="canvasDiv">
                <canvas id="graph" width="600" height="600" ></canvas>
                <div id="gInfo"></div>
        </div>
        <div id="controls">
        <!-- (put your controls here, if you need any) -->
        </div>
</div>

</body>
</html>
グラフの画面 - ThreeDGraph.java
import net.java.ao.EntityManager;
import java.sql.SQLException;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

public class ThreeDGraph extends WebPage {
}
グラフデータ取得処理(Ajax) - ThreeDJson.html
<span wicket:id="json"/>
グラフデータ取得処理(Ajax) - ThreeDJson.java
import net.java.ao.EntityManager;
import java.sql.SQLException;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;

public class ThreeDJson extends WebPage {

        public ThreeDJson() {
                ThreeDData[] data;
                try {
                        EntityManager manager = new EntityManager( "jdbc:mysql://localhost/aotest", "root", "" );
                        data = manager.find( ThreeDData.class );
                } catch ( SQLException e ) {
                        throw new RuntimeException( e );
                }

                StringBuilder json = new StringBuilder();
                json.append( "([" );
                for ( int i = 0; i < data.length; ++i ) {
                        if ( i > 0 ) json.append( "," );
                        json.append( "{x:" + data[i].getX() + ",y:" + data[i].getY() + ",z:" + data[i].getZ() + "}" );
                }
                json.append( "])" );

                add( new Label( "json", json.toString() ) );
        }
}
データのエンティティ - ThreeDData.java

AOのエンティティにとって、主キーなんて大事じゃありません。

import net.java.ao.Entity;

public interface ThreeDData extends Entity {
        /*
         * X座標
         */
        Double getX();
        void setX( Double x );

        /*
         * Y座標
         */
        Double getY();
        void setY( Double y );

        /*
         * Z座標
         */
        Double getZ();
        void setZ( Double z );
}

マイグレーションだってできちゃうよ

マイグレーションすればエンティティからテーブルつくってくれるです。

マイグレーション用ソース - Migration.java
import java.sql.SQLException;
import net.java.ao.EntityManager;

public class Migration {

        public static void main( String[] args ) {
                EntityManager manager = new EntityManager( "jdbc:mysql://localhost/aotest", "root", null );

                java.util.logging.Logger.getLogger("net.java.ao").setLevel(java.util.logging.Level.FINE);

                try {
                        manager.migrate( ThreeDData.class );
                } catch ( SQLException e ) {
                        throw new RuntimeException( e );
                }
        }
}
ビルドファイル - build.sh

ビルド時にマイグレーションしちゃうよ!

#!/bin/bash

SRC_DIR=./src
LIB_DIR=./wicket/WEB-INF/lib
CLASSES_DIR=./wicket/WEB-INF/classes

LIBS=$LIB_DIR/slf4j-api-1.5.6.jar
LIBS=$LIBS:$LIB_DIR/slf4j-jdk14-1.5.6.jar
LIBS=$LIBS:$LIB_DIR/wicket-1.3.5.jar
LIBS=$LIBS:$LIB_DIR/activeobjects-0.8.2.jar
LIBS=$LIBS:$LIB_DIR/mysql-connector-java-5.1.0-bin.jar

SOURCES=$SRC_DIR/*.java
HTMLS=$SRC_DIR/*.html

WEBAPPS_DIR=/var/lib/tomcat6/webapps

javac -classpath $CLASSES_DIR:$LIBS -d $CLASSES_DIR -sourcepath $SRC_DIR $SOURCES
cp $HTMLS $CLASSES_DIR
jar cvf wicket.war -C ./wicket .
sudo cp -p wicket.war $WEBAPPS_DIR/
java -classpath $CLASSES_DIR:$LIBS Migration

気になったので言及


ActiveObjectsをやめた
・・・
なんか、0より大きく1より小さい場合以外はすべてExceptionがスローされるような実装です。というか、エラー内容からも明らかなように、プライマリキーがきっかり1つでなければ必ずエラーになります。プライマリキー指定はテーブルに関連があったりすると増えることもありますから、ちょっと不安です。

ActiveObjectsjavaインスタンスを丸ごとDBにマッピングしてくれるです。
インスタンスの丸ごとマッピングなので、主キーが1個とか2個とか関係ないです。
そう・・・主キーなんて大事じゃないんです。大事なことなので2度言いました。
むしろ、AOを使うならDBのことなんか忘れちゃえばいいんです。

って・・・ぜんぜん説明できていない・・・。

追記

ActiveObjectsをちゃんと知るには、こっちを読んだ方がいいと思う→図解、ActiveObjectsの一番大事なところ!