D3.js

LL祭りが近づいてきた。お誘いを受けたけど行けない。 だって、この時期暑すぎて血が沸騰しちゃうと思うから。何時も思うんだけど、何でこの 熱い時にやるの?

想像するに、サービス業の人なら知ってると思うけど2,8の月は客足が落ちるから。 よって、会場を確保し易い、会場費が安くなるってのがあるんだろうな。どうせやるなら 2月の寒い時にやってくれればいいのに。 熱いのは対処のしようがないけど寒いのは服を余計に着るなりすれば容易に対処出来るからね。

このLLは初回から出席してたなあ。初回は確か渋谷の坂を上った所の映画館みたいな所だった。 perlのあの人や蛇のあの人rubyのあの人とかと指しでお話が出来てとっても楽しかったぞ。 台風も来てて、一時は開催が危ぶまれたけどね。

後、思い出に残るのは、本当のリングでの戦い。馬の被り物(本人曰く、ラクダだそうです) を被って、キャメルをアピールしてたなあ。ああいうお祭り感が大好き。 いじめもあったね。ネットからデータを落としてくるWebクライアントもどきを作れって御題。 PHPは、ページを作るのは得意だけど、そういう方面は苦手。出題者はそれを分かっていて 出題したんだろうな。今ならパワハラですよ。

おいらは大分前にWebの世界から足を洗っちゃったけど、今年のLLはjavascriptの未来なんて のが大きなテーマになってるらしい。世間から遅れていかないように少しは、その方面も 追ってみるかな。

一人LLですよ。で、そのテーマは、

データ・ドリブン・ドキュメント

にしてみます。

4種のWebサーバー

D3 チュートリアル のセットアップを見ると、ページを表示するのに、MAMPを用意しろとな。MAMPって何かと 思ったら、MACにApacheにMySQLにPHPらしい。MACは兎も角として、Apacheぐらいは、準備 しておくか。

sakae@debian7:~/apache_1.3.42$ ./configure
  :
Creating Makefile
Creating Configuration.apaci in src
Syntax error --- The configuration file is used only to
define the list of included modules or to set Makefile in src
options or Configure rules, and I don't see that at all:
 `$(SRCDIR)/apaci`
default
 :

あれ、debianにapache(の古い奴)て、相性が悪いの? だったら、こうしてやる!

sakae@debian7:~/apache_1.3.42$ bash ./configure

今度はちゃんと動いた。変なの。と思っていたら、今度は、こんなエラーが

===> src/support
make[2]: Entering directory `/home/sakae/apache_1.3.42/src/support'
gcc -c  -I../os/unix -I../include   -DLINUX=22 -DHAVE_SET_DUMPABLE -DUSE_HSREGEX -DNO_DL_NEEDED `../apaci` htpasswd.c
htpasswd.c:101:12: error: conflicting types for ‘getline’
In file included from ../include/ap_config.h:1054:0,
                 from htpasswd.c:40:
/usr/include/stdio.h:671:20: note: previous declaration of ‘getline’ was here
make[2]: *** [htpasswd.o] Error 1

コンフリクトの恐怖。apacheのソース中のgetline定義を無効にしたよ。昔は、この関数が stdioで提供されていなかったから、apacheの人が自前で用意してたんだな。FreeBSD6.4と 言う、2006年に出たやつでは、stdioに入っていない。 何箇所かこういのが有ったんで、くぐり抜けて行ったら、やっとサーバーが立ち上がったよ。 やれやれ。この際、ちょっと歴史を紐解いてみると、man歴史書によると

STANDARDS
     The getdelim() and getline() functions conform to IEEE Std 1003.1-2008
     (“POSIX.1”).

HISTORY
     These routines first appeared in FreeBSD 8.0.

ちょっと前は無かったって事なのね。良く使いそうなルーチンなのにね。

もっと簡単に事を済ませるなら、Pythonを使うのがよかろう。apacheで言う、htdocsに相当 するdirに移動して(例では、D3/)そこで、サーバーを立ち上げる。port番号は、1024以上 なら、一般ユーザーでも文句を言われない。

[sakae@fedora ~]$ cd D3
[sakae@fedora D3]$ python -m SimpleHTTPServer 8000

Python3なら、名前がちと違った名前で起動する事。互換性を保ってよねーと思っちゃうぞ。

python -m http.server 8888 &

宗教上の理由で蛇を嫌っている人なら、Rubyのwebrickを使うのがお手軽。レール専用って 訳じゃないんで、どんどん使おう。こんな簡単なコードを書いて、(webrick.rbからパクッて きただけなんで、自慢にもならないが)走らせればよい。ruby1.8系の人は、1.9とか2.1に Upするチャンスですよ。htdocsの事をドキュメントルートって言うのね。コードを眺めて いて気が付いたよ。

   require 'webrick'

   root = File.expand_path '~/D3'
   server = WEBrick::HTTPServer.new :Port => 8080, :DocumentRoot => root
   trap 'INT' do server.shutdown end
   server.start

お次は、javascript版のWebサーバー。実行には高性能V8エンジンを搭載し、C10K問題をクリア する為ノン・ブロッキング I/O方式を採用したnode.jsが必要だ。基本乗りこなしの指南書は、 Nodeビギナーズブックになる。その他の 便利な案内は、node.js 日本ユーザグループが良い。最近、ピンクの スカイラインが売れているようだけど、node.jsは皆さんのお気に召すかどうか? まあ、 趣味の世界だから好きなようにすれば良い。

var http = require("http"),
    url = require("url"),
    path = require("path"),
    fs = require("fs")
    port = process.argv[2] || 8086;

http.createServer(function(request, response) {

  var uri = url.parse(request.url).pathname
    , filename = path.join(process.cwd(), uri);

    path.exists(filename, function(exists){
        if (!exists) { Response_404(); return ; }
        if (fs.statSync(filename).isDirectory()) { filename += '/index.html'; }
        fs.readFile(filename, "binary", function(err, file){
            if (err) { Response_500(err); return ; }
            Response_200(file, filename);
        });
    });

    function Response_200(file, falename){
        var extname = path.extname(filename);
        var headerStr = {
            '.json':{
                'Content-Type':'application/json; charset=utf-8',
                'Access-Control-Allow-Origin':'*',
                'Pragma': 'no-cache',
                'Cache-Control' : 'no-cache'
                },
        }
        headerStr['.topojson'] = headerStr['.geojson'] = headerStr['.csv'] =  headerStr['.json'];
        var header = (headerStr[extname]) ? headerStr[extname] : null;

        response.writeHead(200, header);
        response.write(file, "binary");
        response.end();
    }

    function Response_404(){
          response.writeHead(404, {"Content-Type": "text/plain"});
          response.write("404 Not Found\n");
          response.end();
    }

    function Response_500(err){
            response.writeHead(500, {"Content-Type": "text/plain"});
            response.write(err + "\n");
            response.end();
    }

}).listen(parseInt(port, 10));

console.log("Server running at http://localhost:" + port );

カレントdirが、ドキュメントルートになるんで、そこへ移動してから

[sakae@fedora D3]$ node server.js
Server running at http://localhost:8086

こんな風に起動すれば良い。

折角LLなんで、bashを糊にして色々なコマンドを使って作った、 httpd.bashとか awk_httpなんてのを、楽しそうなんで 揚げておきます。

chrome for Linux

使うブラウザーもチュートリアルに合わせてchromeにしておくかな。でも、fedoraもdebianも標準では 入らないみたい。何たって、chromeはググル様謹製だからなあ。 ぐぐったら、 Chrome download で、出てきた。Linux上のfirefoxから検索するのが吉。打倒、firefoxって事で、喜んで 進呈しますですってさ。Windowsのfirefoxからだと、めんどくさいのかな?

確信犯なんだけど、FreeBSDにもchromeが入るか試してみた。そしたら、chromesetup.exeを お勧めされた。気が利かないgoogleだ事。ついでにワインも送ってこんかい! wineでWindowsの まねをさせて、その上でchromeが動くのだろうか? 暇な時に試してみよう。

で、fedoraは、ダウンロード中に、sudoさせられて、一発で入った。debianは、deb玉が 落ちてくるだけだった。どうやってインストールしろと?

dpkg -i chrome.deb

こうやるのが、正解みたい。馬鹿の一つ覚え、apt-get以外にも、コマンドが有ったのね。 (そりゃ違うって、apt-getは、後発のラッパーでっせ)

で、後で気が付いたんだけど、firefoxもツールメニューにWeb開発なんて項目が有るのね。 毎日使っているけど、初めて気が付いたよ。

D3.jsの初体験

書いたと言うより、コピペしたやつ。

[sakae@fedora D3]$ cat test.html
<!DOCTYPE html>
   <html lang="en">
      <head>
         <meta charset="utf-8">
         <title>D3 Test</title>
         <script type="text/javascript" src="d3.v3.js"></script>
      </head>
      <body>
         <script type="text/javascript">
              var dataset = [ 5, 10, 15, 20, 25 ];
              d3.select("body").selectAll("p")
              .data(dataset)
              .enter()
              .append("p")
              .text(function(d) { return d; });
         </script>

         <svg width="500" height="100">
              <circle cx="25" cy="25" r="22"
                  fill="yellow" stroke="orange" stroke-width="5"/>
         </svg>

      </body>
</html>

さすが、schemeを標榜して開発されたjavascriptらしく、手続きを引数に出来るのね。 今回コピペしてみて、一番の収穫でしたよ。

それ以外の山のようにある手続きは、 API Reference を参照の事。これだけじゃ、どう使うか分からんと言うなら、 Galleryを眺めるのが良かろう。

上で書いたのを折角だから動かしてみる。Windows7に蛇が住み着いているので、そこで やってみる。

C:\homes\D3>ls
d3.v3.js*   favicon.ico test.html   z/

favicon.icoは、rubyのwebrickが厳格で404って言うんで、適当なのを放り込んであります。 断じて、私はdot職人ではありません。zは後述べる。

C:\homes\D3>python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
NIL - - [20/Aug/2013 15:01:24] "GET /test.html HTTP/1.1" 200 -
NIL - - [20/Aug/2013 15:01:24] "GET /d3.v3.js HTTP/1.1" 200 -

これ、http://localhost:8000/test.html にアクセスした時のログ。d3.v3.jsって、8800行 300kを超える大作です。普段使うには、スペースを切り詰めて容量を小さくしたのが吉。

NIL - - [20/Aug/2013 15:02:16] "GET /z/ HTTP/1.1" 200 -
NIL - - [20/Aug/2013 15:02:21] "GET /z/calendar/ HTTP/1.1" 200 -
NIL - - [20/Aug/2013 15:02:26] "GET /z/calendar/01/ HTTP/1.1" 200 -
NIL - - [20/Aug/2013 15:02:27] "GET /z/calendar/01/dji.csv HTTP/1.1" 200 -
NIL - - [20/Aug/2013 15:02:55] "GET /z/LivingCode/ HTTP/1.1" 200 -

実はこれ、清水さんが公開された例を頂いてきて、 放り込んだものです。地図との連携の例がとっても素敵。こういうのを見ると、見せ方の アイデア勝負って事になるなあ。

無線をやってる人なんかも、応用無限大だぞ。世界地図に衛星軌道をリアルタイムに マッピングするとか。CallSignから位置を割り出し、その方向へアンテナを振るとか。 誰かが作ってそれをWebに公開すれば良い。お前がやれよと言われそう。 やってもいいけど、その前に球面三角法の本とかを恵んでください。

node.js

fedoraに勢い尼って、nodejsを入れちゃったので、ちょっとscheme気分を味わってみる。 nodeってコマンドは、Webブラウザーから、javascriptの実行エンジンを抜き出してきた ようなものだ。

[sakae@fedora ~]$ node
> function(x) { x * x }(5)
undefined

lambdaと同様functionって長い綴りだ事。fun ぐらにしておいてくれれば、楽しめたのに。 今は無き、ネットスケープ社で仕様を決めた人を恨みますよ!(補完が効くから、大きな問題じゃ無いよって意見もあるけどね)

無名手続きに値を指定して、評価を試みたんだけど、それが未定義とは? 既出の例を 見直してみると

> function(x) { return x * x }(5)
25
> du = function(x) { return x * x }
[Function]
> du(12)
144

手続きから値を返すにreturnを強要されるって、なんか蛇みたい。

> function(x) { return x * x }
...
...
... (16)
256

こういう所も、蛇っぽいぞ。

geo_example/geo7

上で地図との連携がとても素敵って書いたけど、特に目を引いたのがこれ。群馬県の住宅事情を 間取りと家賃、場所で表したもの。map上に旨くmapされてて不動産業者が即飛びつきそうな 出来栄えですよ。不動産業者に売り込んだらいかがでしょう!

ipad上のサファリでも綺麗に見えました。svgのおかげで、拡大/縮小も思いのまま。 拡大しても図形のジャギィー無し。これなら i電話でも使用に耐えますよ。但し、重いけどね。

ページをロードした後、pythonの一行野郎サーバーを落としても、ちゃんと散布図とmappingが 機能してました。クライアント側にデータが渡ってしまえば、後はjavascritの世界。見方を 変えれば、分散コンピューティングの世界ですな。

データも、geojson って形式で用意しておいて、それを使うようになってるんで、汎用化は 容易に出来ると思います。

地図データはどこかに公開されてるんでしょうか? ざっと見た所、頂点と緯度、経度を 合わせたようなデータでしたけど。

{
"type": "FeatureCollection",
                                                                                
"features": [
{ "type": "Feature", "id": 0, "properties": { }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 139.18030781456972, 36.964513370861035 ], [ 139.16702190728492, 36.951732483443813 ], [ 139.17615111258294, 36.916245125827913 ], [ 139.17331523178825, 36.89091643708619 ], [ 139.15323098013261, 36.871978192053078 ], [ 139.15680496688759, 36.862732443708708 ], [ 139.19541956291408, 36.856050642384204 ], [ 139.19813890066243, 36.841288523178903 ], [ 139.17609284105976, 36.836723920529899 ], [ 139.13909042384122, 36.803645119205392 ], [ 139.12112337086108, 36.794321675496782 ], [ 139.05931670860943, 36.793350483443803 ], [ 139.02507247682135, 36.770993642384191 ], [ 139.02528613907299, 36.746597298013334 ], [ 139.03323049006639, 36.741799609271609 ], [ 139.01798277483459, 36.719734125827898 ], [ 139.02650984105975, 36.711828622516641 ], [ 139.03033633774851, 36.683625205298092 ], [ 139.01456417880809, 36.659228860927229 ], [ 138.9852730264902, 36.644738675496768 ], [ 138.9700835827816, 36.647127807947101 ], [ 138.96866564238425, 36.654741953642464 ], [ 138.95266039735114, 36.653829033112665 ], [ 138.93129417218557, 36.662297827814648 ], [ 138.89320401986768, 36.657150509933857 ], [ 138.87139104635776, 36.668008437086172 ], [ 138.82073366887431, 36.6647258079471 ], [ 138.82471555629152, 36.702466331125912 ], [ 138.8029414304637, 36.718782357615979 ], [ 138.78942243708622, 36.744868576159028 ], [ 138.7953467086094, 36.758135059602736 ], [ 138.81776182119219, 36.761592503311348 ], [ 138.82691045033127, 36.772295039735191 ], [ 138.83176641059617, 36.793894350993469 ], [ 138.8217437086094, 36.817630284768306 ], [ 138.85643468874187, 36.811647741721949 ], [ 138.88187992052994, 36.825788298013336 ], [ 138.92968199337764, 36.833169357615986 ], [ 138.91600760927167, 36.850845052980226 ], [ 138.92313615894054, 36.88359364900672 ], [ 138.94063703973524, 36.895150834437182 ], [ 138.97146267549684, 36.887206483443805 ], [ 138.97952356953658, 36.891771086092817 ], [ 138.97668768874186, 36.920790304635865 ], [ 138.96664556291407, 36.931162635761694 ], [ 138.96654844370875, 36.979081251655735 ], [ 139.04739047019882, 36.987297536423945 ], [ 139.09111353642399, 37.019735350993486 ], [ 139.09699896026507, 37.058621880794817 ], [ 139.12294921192068, 37.047142390728588 ], [ 139.13190360264917, 37.025737317880903 ], [ 139.17098437086111, 37.001981960265006 ], [ 139.1795891324505, 36.990385927152424 ], [ 139.18030781456972, 36.964513370861035 ] ] ] } }
  :
{"type":"Topology","transform":{"scale":[0.0001272991027579707,0.00010733911006995636],"translate":[138.39708027152327,35.98533811920532]},"objects":{"gunma":{"type":"GeometryCollection","geometries":[{"type":"Polygon","id":0,"arcs":[[0,1,2,3,4,5]]},{"type":"Polygon","id":1,"arcs":[[6,-6,7,8]]},{"type":"Polygon","id":2,"arcs":[[9,-1,-7]]}

そして、何よりも大事な物件データ。まあ、これはデータベースから引っ張ってきて、 所定のフォーマットに変換するだけだから、スクリプト一発で済むな。

{
"type": "FeatureCollection",
                                                                                
"features": [
{ "type": "Feature", "id": 1, "properties": { "id": 14388189, "rent": 25000, "room": "1R", "lat": 36.40078694, "lng": 139.0906517, "address": "群馬県前橋市幸塚町", "city": "前橋" }, "geometry": { "type": "Point", "coordinates": [ 139.0906517, 36.40078694 ] } }
,
{ "type": "Feature", "id": 2, "properties": { "id": 4032272, "rent": 28000, "room": "1K", "lat": 36.38146, "lng": 139.06602190000001, "address": "群馬県前橋市紅雲町1丁目", "city": "前橋" }, "geometry": { "type": "Point", "coordinates": [ 139.06602190000001, 36.38146 ] } }
 :

アパマンあたりが、既に共通フォーマットを策定してたりして。

今や、json形式はXML嫌いな人達に大うけだね。何と言っても、eval一発でデータを取り込める んで、超お手軽。まあ、これは先輩のschemeがread一発でS式を取り込めるのを真似したんだ ろうけどね。

geojsonで、扱うデータが大きくなると、きっとbinary形式でって騒ぐ輩が現れるだろうけど、 text文化を大事にして欲しいものです。