2010年12月8日水曜日

Highcharts

最近、個人的に作っているWebアプリでグラフを書きたくなったので、
何かいいグラフ書くもの無いかなーと探してたら、見つけたのでちょっと紹介させていただきます。

Highcharts

とりあえず、サンプルソースはこちら
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Highcharts Example</title>
  
  <script type="text/javascript" src="../../js/jquery-1.4.2.min.js"></script>
  // highcharts.js読み込み
  <script type="text/javascript" src="../../js/highcharts.js"></script>
  // デザイン用js読み込み。無くてもOK
  <script type="text/javascript" src="../../js/themes/gray.js"></script>
  // 詳しくは見れてないですが、コレも必要
  <script type="text/javascript" src="../../js/modules/exporting.js"></script>
  <script type="text/javascript">
  
   var chart;
   $(function() {
    chart = new Highcharts.Chart({
     chart: {
      // グラフを表示する領域を指定
      renderTo: 'container',
      // グラフのタイプを指定
      defaultSeriesType: 'bar'
     },
     title: {
      text: 'Historic World Population by Region'
     },
     subtitle: {
      text: 'Source: Wikipedia.org'
     },
     xAxis: {
      // グラフの縦軸の項目名を指定
      categories: ['Africa', 'America', 'Asia', 'Europe', 'Oceania'],
      title: {
       text: null
      }
     },
     yAxis: {
      min: 0,
      title: {
       text: 'Population (millions)',
       align: 'high'
      }
     },
     tooltip: {
      // グラフにマウスを乗せたときのtooltipの表示を指定
      formatter: function() {
       return ''+
         this.series.name +': '+ this.y +' millions';
      }
     },
     plotOptions: {
      bar: {
       dataLabels: {
        enabled: true
       }
      }
     },
     legend: {
      layout: 'vertical',
      align: 'right',
      verticalAlign: 'top',
      x: -100,
      y: 100,
      floating: true,
      borderWidth: 1,
      backgroundColor: '#FFFFFF',
      shadow: true
     },
     credits: {
      enabled: false
     },
      // 表示したいデータをJSON形式で指定
     series: [{
      name: 'Year 1800',
      data: [107, 31, 635, 203, 2]
     }, {
      name: 'Year 1900',
      data: [133, 156, 947, 408, 6]
     }, {
      name: 'Year 2008',
      data: [973, 914, 4025, 732, 34]
     }]
    });
   });
  </script>
 </head>
 <body>
  <div id="container" style="width: 800px; height: 400px; margin: 0 auto"></div>
 </body>
</html>

デモはこちら

こんな感じに、簡単にできます。
オプションの簡単な説明をすると、まず

22行目
グラフのタイプを指定します。
"line"と指定すると折れ線グラフになったり、"colum"と指定すると上下の棒グラフになります。

73行目
seriesというオブジェクトにJSON形式でデータを定義してあげます。
nameとdataというKEY値で指定してあげてください。


横軸のメモリとかは、データの大きさによって自動でスケールアウトしたり、
グラフの項目名をクリックすると、その項目を非表示にできたりとかなりいろいろできるので
おすすめです。今回は紹介していないですが、自動で時間経過に応じてグラフを描画するなんてことも
できるので、かなりUI的に良いものができそうです。今度はそっちの紹介でもしようかなー。

2010年11月26日金曜日

JSONのValue値

JSONの書き方について、最近知ったことを紹介させていただきます。
JSONを使い慣れている人にとっては、常識かもしれないけど、知ったときは
「へぇ~~」ってなったのでね。

そもそも、JSONって何かと言うとJavaScriptでよく扱われるデータの書式のことです
(合ってるかな??

一般的な形は以下のとおり。

{"KEY":'VALUE',"KEY":'VALUE'}

みたいな形で、KeyとValueの形でデータを表します。
ちなみに、これはクライアント側だけではなく、サーバ側でも使えます。
だいぶ前に紹介したJSONIC
なんていうのは、JSON形式の文字列をJavaの
クラスにエンコードしたり、逆にクラスをJSON形式の文字列にデコードしたりしてくれる
ライブラリです。

それで、注目して欲しいのが、VALUEを「'」で囲っている所。
なんとなく、自分は今まで「'」を使ってたんだけど、コレだと問題があるのです。
例えば、value値に「"」を含みたい場合は、
{"KEY":'"hoge"'}
で、問題無くvalue値が「"hoge"」として認識されます。
「'」をvalue値に含みたい場合は、
// パースできない
{"KEY":''hoge''}
// こっちもパースできない
{"KEY":'\'hoge\''}
上記みたいに記述したらパースJSONICがパース出来ずにExceptionをはいてしまいました。

で、代わりに下記の記述方法を試してみます。
{"KEY":"VALUE"}
key値・value値ともに、「"」を囲み文字として使用してみます。
この方法だと、
{"KEY":"'hoge'"}
{"KEY":"\"hoge\""}
上記の様にすることで、それぞれvalue値に「"」「'」を含める事ができます。
もちろん問題なくJSONICパース出来ます。

どうやら、正式にはJSONのvalue値は["]で囲ってやるのが正しい形のようです。
今までは、なんとなく「'」を使用していたので、今後は「"」を使用していこうと思います。

2010年11月23日火曜日

Slim3のGlobalTransaction

今更感がすごいあるけど、Slim3のトランザクションについてメモ。
まずは、普通のトランザクション。

public void update(final Key key) {
    // トランザクション開始
    final Transaction tx = Datastore.beginTransaction();
    fianl Model model = Datastore.get(tx, Model.class, key);

    mode.setName("hoge");
    Datastore.put(model)

    // コミット
    tx.commit();
}

GAEはデフォルトのトランザクションだと、単一のエンティティにしかトランザクションを貼れない。
でも、複数のエンティティを一緒に更新したい時ってのがあるわけで、そういう時に
使用するのが、グローバルトランザクション。

public void update(final Key hogeKey, final Key fugaKey) {
    // トランザクション開始
    final GlobalTransaction gtx = Datastore.beginGlobalTransaction();
    fianl HogeModel hogeModel = gtx.get(HogeModel.class, hogeKey);
    fianl FugaModel fugaModel = gtx.get(FugaModel.class, fugaKey);

    hogeModel.setName("hoge");
    fugaModel.setName("fuga");
    gtx.put(hogeModel, fugaModel)

    // コミット
    gtx.commit();
}

上記の様にすれば、複数のエンティティにまたがる処理を同一トランザクションで処理出来ます。
ちなみに、上記で使用しているputメソッドの引数は可変長引数なので、
2つ以上のエンティティの更新が可能です。

ここで、注意して欲しいのがqueryメソッドはトランザクションのサポート外ってところ。
なので、エンティティのプロパティにKeyを生成するための文字列を持たせておくのが
ベターでしょうか。(更新時等にKeyでエンティティを取得できるようにするため)

2010年10月3日日曜日

NicoBot ~Chrome Extensions~[内部的な事]

今回は、前回紹介させていただいたChrome Extensions「NicoBot」を
作成するにあたって使用した技術について書かせていただきます。

まずは、なんといってもChrome Extensionsで
クライアント側の処理を記述してます。
サーバ側はGoogle app engine(slim3)を使ってます。
Twitterへのアクセスには、Twitter4jを使用してます。

処理の流れは以下のとおり。

  1. Extensionのアイコンのクリックイベントで、ユーザを一意に特定できるようにする為に現在日付とホスト名を取得し、それを合わせた文字列(①)をサーバ側に渡す。
  2. サーバ側では、リクエストを受け取ったらtwitterにアプリケーションの認証ページへのリダイレクト処理を行う。
  3. ユーザが認証を許可した場合、サーバ側でtwitterから発行されたoAuth情報と先程クライアント側から受け取った文字列(①)を紐付けてBigTableに登録する。そして、認証完了のペーシで画面遷移を行う。
  4. クライアント側では、認証完了ページへの遷移をハンドルして先程の作成した文字列(①)をlocalStorageに保存し、Extensionのアイコンを認証完了後のアイコンに変更する。
  5. この状態で、Extensionのアイコンをクリックすると、ON ⇔ OFF の切り替えが行えるようになる。
  6. ちなみに、このON ⇔ OFF の状態もlocalStorageに保存する。
  7. ONの状態で、ニコニコ動画のURLに遷移すると、クライアント側ではURLからニコニコ動画の動画のID部分を抜き出し、ユーザを特定するための文字列(①)とID部分(②)をサーバ側に渡す。
  8. サーバ側では、受け取った動画ID(②)を元に、ニコニコ動画APIを使用して動画情報を取得する。
  9. その後は、ユーザ情報(①)でBigTableを検索し、TwitterのoAuth情報を取得しTwitterにPOSTする。
ただ、今のままだとExtensionをアンインストールしてもlocalStorageに
残ったままになってしまうので、オプションページを作成して、
そこからlocalStorageの情報を削除できるようにしたいなぁー。


そうそう、実は地味にアップデートさせました。
Twitterに投稿した際に、画面に右下にポップアップウインドウ出るように
してみました。これで、誤爆にも気付けるはず!

以下、参考サイト

2010年9月26日日曜日

NicoBot ~Chrome Extensions~

少し前に、ChromeのExtensionsを作成したので、その事について書いてみようと思います。

どんなエクステンションかというと、ニコニコ動画を視聴しようとすると自動で
何の動画を視聴しようとしているかというのをTwitterにPOSTしてくれるというもの。


まずは、以下がインストールページ↓
https://chrome.google.com/extensions/detail/bdemcondenlehbgnmcdiecdngllnaabl?hl=en

インストールしていただくとブラウザ上部にニコニコ動画のアイコンが表示されるので、
クリックしてください。

すると、NicoBotを許可するかのページが表示されるので、いいよーという方は
許可してください。すると、アイコンが以下の様に変化します。


この状態で、ニコニコ動画の視聴ページ。URLが「http://www.nicovideo.jp/watch/xxxx」の
ページに遷移しても、何も起こりません。

アイコンを再度クリックすると、以下の様に変化します。



この状態で、ニコニコ動画の視聴ページに遷移すると、その動画の情報をTwitterに
POSTします。

POSTする内容は以下のような感じです。

【ニコニコ動画】"動画タイトル" を視聴しようとしています。"動画URL" #nicovideo #"動画ID" #NicoBot


アイコンをクリックするたびに、ONとOFFを切り替えられます。
ちなみに、POSTするタイミングは動画ページに遷移した場合ですので、実際に再生ボタンを
クリックしなくてもPOSTされてしまいます。
再生ボタンをクリックしたというイベントをハンドル出来ればいいんですけど、
どうやるんでしょ??

2010年7月7日水曜日

slim3使用時のgoogle認証の使い方ではまったところ

slim3使用時のgoogle認証の使い方のハマリどころ。

例えば
http://アプリケーションID/private/
というURLにアクセスした場合に、private下にはすべてgoogle認証をかけたい場合、
以下のようなFilterを作成します。

/**
 * google認証を行うFilterクラス
 */
public class MemberFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
             FilterChain filterChain) throws IOException, ServletException {

    final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    final UserService userService = UserServiceFactory.getUserService();

    // リクエストのあったURL
    final String requestUri = httpServletRequest.getRequestURI();

    // googleアカウントにログインしているかを判定するUtilクラス(後述)
    if (AccountUtils.isGoogleLogin(httpServletRequest) == false) {
      ((HttpServletResponse) response).sendRedirect(userService.createLoginURL(requestUri));
      return;
    }

    // ログイン成功
    filterChain.doFilter(request, response);

  }

  @Override
  public void destroy() {
  }

  @Override
  public void init(FilterConfig arg0) throws ServletException {
  }
}

/**
 * google認証を判定するUtilクラス
 */
public class AccountUtils {

  /**
   * googleアカウントにログインしているかを判定する。
   *
   * @param request
   * @return ログインしていればtrue, していなければfalse
   */
  public static boolean isGoogleLogin(final HttpServletRequest request) {

    final UserService userService = UserServiceFactory.getUserService();
    final Principal principal = request.getUserPrincipal();

    if (principal == null || userService.isUserLoggedIn() == false) {
      return false;
    }

    return true;
  }
}


以上です。
この例だと、「/private/」にアクセスした場合に、MembeerFilterクラスのdoFilterが呼ばれ、
17行目のif文でgoogleアカウントにログインしているかをチェックし、
falseならばログインページにリダイレクトされます。

ここで、注目して欲しいのが、14行目のrequestUri。
これがログイン後にリダイレクトされるパスを示しています。
18行目のuserService.createLoginURL(requestUri)では、ログインページのパスを作成し、
このパスには、ログイン後にアクセスするURL、つまり今回の例でいう「/private/」が含まれています。
ただ、ここでハマリどころなのですが、実際にアクセスしようとしたURLが「/private/」となっていても、
14行目で取得できるurlは「/private/index.jsp」
つまり、ログイン後に飛ばされパスは「/private/index.jsp」となってしまいます。
Slim3では、jspファイルに直接アクセスできないようになっているので、
もちろんこれは403エラーになってしまいます。

これを解決するためには、MemberFilterクラスの18行目を、
((HttpServletResponse) response).sendRedirect(userService.createLoginURL(requestUri.replace("index.jsp", "")))

と修正して上げれば、ログイン後に飛ばされるパスから「index.jsp」が削除されるので、
問題なくControllerを経由してページにアクセスできるようになります。

2010年6月22日火曜日

JSONICの使い方

最近は、JSON形式のデータを熱かったアプリを作ろうと考えているので、
JSONICについて調べてみました。

JSONICっていうのは簡単にいうと、JavaでJSON形式のデータを扱うためのライブラリです。
ダウンロードはこちらから↓
JSONIC

使い方はいたって簡単。

// JSONICをインポート
import net.arnx.jsonic.JSON;

public class JsonicSample {
    public static void main(String[] args) {

        final JsonicSample jsonicSample = new JsonicSample();
        jsonicSample.go();

    public void go() {
        // エンコード(Java → JSON)対象のクラス
        final Hoge hoge = new Hoge();
        hoge.setName("sample");
        hoge.setAge(10);

        // JSON形式にエンコード
        final String hogeJson = JSON.encode(hoge);

        // {"name":"sample","age":10} が出力されます
        System.out.println("エンコード結果:" + hogeJson);


        final String hogeJson2 = "{\"name\":\"sample2\",\"age\":20}";

        // JSON形式のデータをJavaのclassに変換(デコード)
        final Hoge hoge2 = JSON.decode(hogeJson2, Hoge.class);
        System.out.println("hoge2.name:" + hoge2.getName());
        System.out.println("hoge2.age:" + hoge2.getAge());

        // 配列を含むJSON形式のデータを用意
        final String hoges = "{\"id\":\"10\",\"hoges\":[{\"name\":\"test\",\"age\":1},{\"name\":\"sample2\",\"age\":2}]}";

        final HogeList hogeList = JSON.decode(hoges, HogeList.class);

        // hogesのようにHogeの形式の配列を持つJSONを、HogeクラスのListをもつクラスにデコードすることが出来ます
        System.out.println("hogeList.id:" + hogeList.getId());
        for (final Hoge tmp : hogeList.getHoges()) {
            System.out.println("tmp.name:" + tmp.getName());
            System.out.println("tmp.age:" + tmp.getAge());
        }
    }

    /**
     * JSONに対応するクラス
     * 各フィールド名をJSONのKEY名と同じにする
     * JSONのKEY名が 「sample-name」のような場合は「sampleName」とする
     */
    class Hoge {

        private String name;

        private int age;

        public void setName(final String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setAge(final int age) {
            this.age = age;
        }

        public int getAge() {
            return age;
        }
    }

    /**
     * HogeクラスのListを保持するクラス
     */
    class HogeList {

        private String id;

        private List<Hoge> hoges;

        public void setId(final String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }

        public void setHoges(final List<Hoge> hoges) {
            this.hoges = hoges;
        }

        public List<Hoge> getHoges() {
            return hoges;
        }
    }
}

こんな感じです。フィールドの型の自動変換についてはかなり柔軟に対応してくれるみたいです。
詳しくは、JSONICのページを見てください;

2010年6月17日木曜日

Java7 使ってみました。(TransferQueue)

今度リリースされる、Java7の新クラスの1つである「TransferQueue」を使ってみました。
説明によると、TransferQueueはQueueのクラスで、あるスレッドがこのQueueにオブジェクトを
追加すると、別のスレッドがQueue内の先程追加されたオブジェクトを削除しないと
オブジェクトを追加したスレッドに制御が戻らない。とのこと。
言葉で説明しても、うまく伝わらないので、早速使ってみます。

その前に、まずはJava7のダウンロードから。
ダウンロードはこちらから↓

早速、Eclipseの設定画面でJava7を指定してみたけど、コンパイラー準拠レベルには1.6までしか
現れない・・・。まだ、正式リリース版だからでは無いからでしょうか?
仕方ないので、基本設定は1.6のままで勧めます。

Javaプロジェクトを作成して、ビルドパスから
「JREシステム・ライブラリー」をjre7に変更してやります。
これで、先程ダウンロードしたJava7が使えるようになります。

下がサンプルソース。
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;

public class TransferQueueSample {

 private TransferQueue queue;

 public static void main(String[] args) {

   final TransferQueueSample sample = new TransferQueueSample();

   sample.go();
 }

 protected void go() {

  queue = new LinkedTransferQueue();

  final Thread threadA = new Thread(new ActionA());
  final Thread threadB = new Thread(new ActionB());

  threadA.start();
  threadB.start();
 }

 class ActionA implements Runnable {

  public void run() {
   try {
    System.out.println("ActionA:testを追加します。");
    queue.transfer("test");
    System.out.println("ActionA:testを追加しました。");
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }

 class ActionB implements Runnable {

  public void run() {
   try {
    System.out.println("ActionB:5s間待機します。");
    int i = 0;
    while (i++ &lt; 5) {
     Thread.sleep(1000);
     System.out.println(i);
    }
    queue.remove();
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }

}

簡単に説明すると、まずActionAは、TransferQueueに文字列"test"を 追加します。この際に使用するメソッドはtransfer(Object)。 このtransferメソッドが、queneにオブジェクトを追加し、そのオブジェクトが 削除されるまで、制御をActionAに返さないようにしています。 transferが実行された後は、メッセージを表示します。 次に、ActionBです。ActionBは5s停止後、quene内のオブジェクトを削除します。 実行してみるとわかると思いますが、ActionAがtransferメソッドを呼び出した後に、 ActionBのカウントダウンが表示された後に、ActionA側の"testを追加しました"との メッセージが表示されたと思います。 Java6まででは、このような動きを実現するには、while()でquenu内の監視をしなくては いけませんでしたが、transferメソッドを使用することで簡単に実現できるようになっています。