2010年2月10日水曜日

SVG Graph Generator

Note: I recommend you to read this entry with Firefox 3.6 (or later).

HTML 5 supports SVG image in HTML document, and recently it was implemented in Firefox 3.6.
I wrote a dynamic graph data visualizer with SVG and Javascript in 2007, and now I can use it in HTML. Hooray!

A sample of it is shown below.
Currently you need to use Firefox 3.6 and enable HTML 5 to see it.
The way to enable it is:
  1. Input "about:counfig" to the location bar
  2. Change the value of "html5.enable" to "true"
When you reload this page after the process, you will see the example here.

The arrangement of nodes bases on attraction repulsion model, and you can drag and drop each of them.

Here is the script file.
SVG Graph Generator (Apache Licence 2.0)

All you need to write in your HTML file is like this.
<!-- Include the script file. -->
<script src="URL_TO_THE_SCRIPT"></script>

<!-- Add svg element to HTML. "id" attribute is required. -->
<svg viewBox="0 0 200 150" id="svg"></svg>

<script>
// Create a graph object.
// The argument is the id of svg element.
var graph = new Graph("svg");

// Add Nodes to the graph object.
// The argument is text appears on it.
// Each node is assigned an ID which starts with 0.
graph.addNode("Alice"); // Node ID 0
graph.addNode("Bob"); // Node ID 1
graph.addNode("Charlie"); // Node ID 2
graph.addNode("Dave"); // Node ID 3
graph.addNode("Eve"); // Node ID 4

// Connect nodes.
// The arguments are two node IDs
// and connection strength (between 0 and 1)
// Currently connection strength has
// no influence on the graph.
graph.joint(0, 1, 1);
graph.joint(1, 2, 1);
graph.joint(1, 3, 1);
graph.joint(3, 4, 1);

// Start to move.
// You can stop it by "graph.sleep()"
graph.wake();
</script>
If it appeals to you, please create something with it!

2010年1月7日木曜日

単純な .screenrc の紹介

GNU Screen、ターミナルでリモートログインして作業するにはほとんど必須のアプリケーションですよね。
今回はその設定ファイル .screenrc に関して。
ここで紹介する .screenrc をホームディレクトリに置けば、こんな感じに仕上がります。それでは行ってみましょう。

現在の僕の .screenrc

defutf8 on
defencoding utf8
encoding utf-8 utf-8

escape ^z^z

bind x
bind ^x

startup_message off
autodetach on
vbell off

caption always "%-w%10L>%{=b bw}%n %t%{-}%+w%-0="
hardstatus alwayslastline "%H%=%Y/%m/%d %02c"
shelltitle "$ |shell"

bindkey -k kD stuff \177

termcapinfo xterm* ti@:te@

shell $SHELL

解説

defutf8 on
defencoding utf8
encoding utf-8 utf-8
文字コードを UTF-8 にする。
web 上の設定サンプルには文字コードの指定に kanji とか defkanji といったコマンドを使っているものがあるが、それらは 3.9.10 以前の古いバージョンでのコマンドで、最新バージョンではすでに無効。現在は encoding と defencoding を使う。
escape ^z^z
エスケープ文字を C-Z に設定。C-Z 自体を入力したいときには それに続けてさらに C-Z を入力。
bind x
bind ^x
デフォルトで C-Z x と C-Z C-X にバインドされている lockscreen を無効化
startup_message off
autodetach on
vbell off
それぞれ
  • 起動時のメッセージを非表示
  • ターミナルが落ちたら自動的にデタッチ
  • 音声ベルが鳴らない時に可視ベルが "Wuff, ---- Wuff!!" とうるさいので可視ベルをオフにする
caption always "%-w%10L>%{=b bw}%n %t%{-}%+w%-0="
hardstatus alwayslastline "%H%=%Y/%m/%d %02c"
caption はウィンドウごとにあるけれど、hardstatus は一つの screen プロセスに一つ。書式は以下の通り。
  • %-w 表示しているウィンドウより前のウィンドウ番号とウィンドウ名
  • %10L> ここを左から10%くらいに配置
  • %{=b bw} 文字のスタイルの変更
  • %n %t 表示しているウィンドウのウィンドウ番号 (%n) とウィンドウ名 (%t)
  • %{-} 文字のスタイルを元に戻す
  • %-w 表示しているウィンドウより後のウィンドウ番号とウィンドウ名
  • %-0= ここを右端にする
  • %H Hostname
  • %Y/%m/%d %02c 日付と時刻
shelltitle "$ |shell"
shelltitle でウィンドウ名を設定。
screen はシェルから出力されたエスケープシーケンスを頼りにプロンプトを特定して、その後に入力されたコマンドを拾ってここに表示する。
詳しくいえば、"^[k^[\" ("^[" はエスケープ文字) が出力されてから、上記 shelltitle で "|" の直前までに入力した文字列までを読み飛ばして、その後に入力された文字列をコマンドとして解釈してくれる。なので、shelltitle の "|" までに書く文字列はシェルの末尾と一致している必要があり、またシェルは上記のエスケープシーケンスを出力しなければならない。シェルの設定はまたの機会に。
"|" 以降の文字列はデフォルトのウィンドウ名。
bindkey -k kD stuff \177
Mac の delete キーを BackSpace として使うためのキーバインド。
termcapinfo xterm* ti@:te@
マウスホイールを利用可能にする。
shell $SHELL
Cygwin 上で screen を実行したときにログインシェルは zsh なのに bash が立ち上がったので追加。

情報募集

上記の設定をしていても、一度デタッチしたスクリーンをレジュームすると UTF-8 の文字が全て "?" になってしまいます。
レジューム時の screen にコマンドラインオプション -U を付ければ UTF-8 モードになってうまくいくのですが、.screenrc での設定だけで完結することはできないのでしょうか。
そもそもなぜデタッチで設定が無効になるのかよく分かりません。

2009年12月27日日曜日

MySQL でレコードのランダム抽出をするには

ORDER BY RAND() で OK。

ただ、全レコードに対してランダムな数値を与えてインデックスなしでソートするのでそれなりのコストがかかる。

この場合の計算量はどうなんだろう。ソートの実装がどうなっているのかは知らないので確かなことは分からないけれど、LIMIT をつけずに全レコードをランダムソートするならば計算量はよくて O(N log N)程度のはず。LIMIT で K 件(K < log N)を抽出する場合は、愚直にやっても O(KN)あればできる。

よって、1件ずつランダム抽出していくような場合は大して問題にならないが、リストとしてまとめてランダムに並んだレコードを取り出したい、というような場合にはあまり効率的でない。
そういった場合には事前にインデックスつきのフィールドに RAND() を書き込んでおいて、取得したいときにそのフィールドで ORDER BY してやったほうがいいはず。

2009年12月26日土曜日

Django の ModelForm / ModelFormSet まとめ

ModelForm の扱いが分かってきたのでまとめ。

なぜ ModelForm を使うのか (普通の Form との違い)

普通のフォームはデータベースの構成とは独立しているので自由に入力フォームを作成することができる反面、データベースの構造を反映したフォームを作成する際には、モデルの記述とほぼ同じ内容をもう一度書き下す必要が出てくる。これは冗長であるし、またモデルの変更をフォームに反映しなければならない点で保守性が低い。
きれいに構成された Web アプリケーションであればデータの入出力はデータベース上での生成、変更、削除にほぼ対応しているわけで、そのようなシーンにおいて、モデルを反映した入力を実現するためにモデルから半自動的にフォームを生成するものが ModelForm である。

ModelForm の定義

モデルフォーム内のサブクラス "Meta" の属性として各種の定義を書く。
  • model 属性: フォームのもととなるモデル
  • fields 属性: モデルの中でこのフォームに含めたいフィールド
各々のフィールドがどのようなフォームで表示されるか (テキストなのかチェックボックスなのかプルダウンメニューなのか等々)は、モデルで指定したフィールドのタイプに基づいて適当なものが選択される。
とはいえ実際の運用においては表示の仕方を変えたいことも多々ある(最もよくあるのは hidden にしたい場合など)ので、これは変更することが可能。
これは Meta 内ではなく、モデルフォーム自身の属性として記述。

ModelFormSet

一度に複数のレコードを編集する場合、同じモデルフォームをそのまま複数並べるわけにはいかない。(同じ name を持つ input 要素が複数作成されて、submit されてもどれとどれを組み合わせるのか分からなくなるから。)
FormSet はこの問題を解決するためのもの。ひとまとめに扱う input 要素ごとに id (フォームセットの内部的なもの。HTML の id 属性ではない) を作成してフォームの name 属性を適宜変更、送信されたデータをきちんとグループ分けして一つ一つのフォームを切り分けてくれる。
ModelFormSet は、上述の Form と ModelForm の関係ように、Model を反映した FormSet。

ModelFormSet の定義

modelformset_factory に、ベースにしたいモデルを渡す。
フィールドの表示の種類を変更するためには、意図する表示を定義した ModelForm を予め定義しておき、引数に "form=モデルフォーム名" を追加してやればよい。
ここで、上述の引数として渡したモデルフォームにおいて、fields 属性が定義されていても、新たに作成される ModelFormSet はそれらを無視して全てのフィールドに関してフォームを作成する点に注意。
これを抑制するには、modelformset_factory の 引数に "fields=フィールド名リスト" を与える。

データ受信後の処理

POST でデータを受け取ってからは、送信されたデータをデータベースに書き込むことになる。
ここで、既存のデータを変更する場合には注意すべき点がある。
基本的に、ModelForm のデータを保存する場合はモデルフォームオブジェクトの save メソッドを利用する。
これは新規作成(SQL でいうと INSERT)でも上書き(SQL でいうと UPDATE)でも同じで、Django はそれらを自動的に使い分ける。
使い分けの基準は、"保存しようとしているデータの主キー値と同じ主キー値を持つレコードがあれば UPDATE、それ以外は INSERT"となっている。
よって、既存のデータを UPDATE するためには、必ず主キーをフォームの中に含めなければならない。
また、一部のフィールドのみを用いたモデルフォームの場合、除外したフィールドが必須であった場合、その値を与えなければ is_valid が False となり保存できない。
このような場合は、受信後にフォームインスタンスを作成する際に、POST データとともにもとのデータベースレコードを渡してやることで、フォームに含まれているフィールドはその値を、そうでないフィールドはデータベースの値を用いて、不足するデータを補ったフォームオブジェクトを作成することができる。
渡し方は、モデルフォームの場合は引数 instance、モデルフォームセットの場合は引数 queryset。

2009年12月25日金曜日

ruby in HTML

ruby と言っても言語の方ではありません。HTML の ruby (ルビ)タグです。
そもそも ruby というタグが HTML のセマンティクス的に許されるかどうかというのは十分議論の余地があるとは思いますが、文字列に近接して付記すべき情報があるのも事実。
で、ここ12時間くらいどうすべきか迷ったことをメモ。

ruby タグの使い方

  1. ルビを振りたい部分を <rb> でマークアップ
  2. ルビとして表示したい文字列を上記 <rb> に隣接する <rt> 内に記述
  3. <rb> と <rt> を <ruby> でマークアップ
例えば
田んぼと<ruby><rb>案山子</rb><rt>かかし</rt></ruby>ののどかな風景

と書くと
田んぼと案山子かかしののどかな風景
となります。

ブラウザ互換性の問題

となります、と書きましたが、上記のサンプルは Internet Explorer もしくは Google Chrome 以外のブラウザではきちんと表示されなかったことと思います。というのは主要なブラウザのうち上記の2つ以外には ruby タグが実装されていないためです。そのため互換性を実現するにはそれなりの workaround が必要になります。
(正直なところそういう見苦しい HTML は書きたくないのですが、差し迫って必要なのだから仕方がない。ちなみに CSS 3 には ruby が含まれているようなのでそのうち他のブラウザでも利用できるようになるのでしょう。)

Firefox における ruby

この手の話はあらゆるブラウザに対応することを目指すときりがなくなるので、今回のサイトが研究室内の他のメンバーに見てもらうためのものであることを踏まえて Firefox できちんと表示できればそれでよしとすることにします。
手っ取り早い方法としては、そのためのアドオンを導入する手があります。しかしながら今回のサイトはどちらかというと周囲にお願いして見てもらうものであるので、できるだけ利用のためのハードルを低くしておくためにもユーザ側では作業をさせたくありません。なので CSS で対処したいと思います。
調べたところ、こちらのサイトに書かれている CSS がベストな解となりそうです。
簡単に言うと、ruby を inline-table、rb を table-row、rt を table-header-group とするというものです。
ただ、そのまま上記のタグの属性を変更すると、もともと ruby をきちんと表示できているブラウザにおいて表示が崩れるので、span に class を設定してクラスセレクタで CSS を設定するのがよさそうです。
先ほどの例をこの方法でマークアップすると以下のようになります。
<style>
#ruby{
display: inline-table;
vertical-align: bottom;
}
#rb{
display: table-row;
}
#rt{
display: table-header-group;
}
</style>
田んぼと
<span class="ruby">
<span class="rb">
案山子
</span>
<span class="rt">
かかし
</span>
</span>
ののどかな風景

そしてこれを表示するとこのようになります。
田んぼと案山子かかしののどかな風景

意図した通りになっているのではないでしょうか。
上記の場合、ルビのサイズを地の文よりも小さくするために rt の font-size を50%に、また周囲の語との垂直方向の位置会わせのために ruby の vertical-align を bottom に設定しています。
とりあえずこれだと Firefox 以外に Chrome、Safari でもきちんと表示できるようです。

さらなる問題

さて、ここまででルビを振ることはできた訳ですが、ちょっとした問題に遭遇しました。
<rb> 内に、間に空白を挟んだ複数の <span> を配置すると、空白が反映されません。
SPAN1 SPAN2RUBY

中身だけであれば
SPAN1 SPAN2

と、きちんと間に空白が表示されるので、おそらく table-row の中に直接 span を複数配置しているのが何らかの影響を与えているものと思われます。
これは、上記の複数の span を、さらに一つの span にまとめてやることで回避できます。
SPAN1 SPAN2RUBY
あまり詳しくないので確定的なことは言えないのですが、おそらく table-row の中には table-cell に相当する要素が必要だということなのではないかと推測しています。

以上、とても胸を張れないバッドノウハウでした。

2009年12月22日火曜日

Django の分からないところ

調べてみたけど分からなかったことリスト。(随時更新)
知っている方、教えていただけると本当に助かります。
あるモデルの一部のフィールドのみ編集可能にしてそれ以外は表示だけさせたい

複数のフィールドを持つモデルの値のうち一部を書き換える場合。例えばこんなかんじで。オブジェクトとそれをインスタンスとして作ったフォームオブジェクトをコンテキストに突っ込めばテンプレートから参照はできるけれど、なんだか冗長だし、複数のインスタンスを一度に編集できるようにしたい場合などはテンプレートにリストが2つ渡って対応付けしたりとややこしくなる。
フォームオブジェクトから value を抜き出して表示することがテンプレートからできれば万事解決なんですが、そういう方法ってないのだろうか。

複合キーを扱いたい

とりあえず現時点では扱えないらしい?
複合キーにすべきフィールド以外に主キーをたてておく。
データモデリング的によろしくないが実用上は複合キーで検索してリレーションには主キーを使えばまあ問題ないか。
(追記) モデルオブジェクトの Meta 内で unique_together(フィールド名リスト) で指定可能だった。

ModelForm での is_validがよくわからない

何をチェックしているのかよくわからない。モデルで指定した「省略可能」 (blank=True) とか 「ヌル値許容」 (null=True) とかの条件にフォーム内データが合致しているか否かをチェックしているのだろうとは思うけれど、暗黙の主キーである id フィールドがフォームに入ってると問答無用で False を返される。なぜ?
追記: 主キーの値の重複が不正と見なされている。下に書いたように主キーを指定した場合は update になるので問題ないはずだが、なぜか invalid となっている。なぜ?
追記: ここに答えがあった。つまり
  1. 主キーは POST パラメータではなく URL で渡す
  2. そこから取得した主キーをもとにして既存レコードのインスタンスを取得する
  3. そのインスタンスと POST パラメータを一緒に渡してインスタンスを作り直す
が正解の模様。

Django は insert と update を勝手に呼び分ける

save が呼ばれたデータの Primary Key が既にデータベースに存在すれば update、なければ insert を実行する。Primary Key が Python 的に False である場合は update する。例えば Primary Key の値が入ってないデータは insert になるので、Primary Key 以外に unique 制約を持つデータなどは、既存データのつもりでうっかり Primary Key のフィールドを入れずに save したりすると insert されて unique 制約に引っかかる、とかありそう。

メモリリーク?

大量データ(300万レコードほど)をデータベースに突っ込むために Django のモデルをインポートして作成したスクリプトをコマンドラインで走らせているとどんどんメモリを食う。やっていることはレコードごとに関数を読んで、その中で新たにモデルのインスタンスをローカル変数に代入して save するというだけ。関数から抜けた後にどこかの時点で GC にかかることを期待していたのだけれど、どうもそうなってはくれない模様。

2009年12月6日日曜日

あす楽がよかった

3週間ほど前から友人達と今日鍋をする約束をしていました。
せっかくの機会だから外で食べると高いカニでも食べようということで楽天の店に注文していたのですが、3日前に「その商品が受注可能でないのに誤って掲載されていたのでキャンセルとします」という連絡が来たとのことで、その夜に急遽あす楽対応の店に切り替えて注文し直しました。

あす楽だと正午までの受注は翌日着になるものの、旭川の店とのことできちんと夕飯に間に合うのか半信半疑だったのですが、驚いたことに朝の10時に着きました。どうやって発送するのか知らずに注文していたのですが、航空便で届くんですね。

Amazon のお急ぎ便でもかなりすごいと思っていたのですが、まさか冷凍の食品を航空便で送ってもらえるなんて夢にも思っていなかったので驚きでした。
コスト的に大丈夫なのか気になりますが、利用する側としてはありがたいサービスです。