Table の折畳み
Rails をいじっていたら、テーブルを折畳むような JavaScript が必要になりました。Google 先生に聞いてみても、機材(折りたたみ式テーブル)の説明ばかり出たので自分で実装してみました。
以下のコードは、TABLE タグの要素が ヘッダ(thead)、ボディ(tbody)、フッタ(tfoot)に明確に分かれている必要があります。方法は至ってシンプルで、TABLE タグに ID を振り、折畳むテーブルを識別できるようにします。後は、tbody タグのスタイルをいじってやれば完了です。
/** * Table を折畳む */ function fold_table(id) { var table = $(id) var tbody = table.getElementsByTagName('tbody')[0]; tbody.style.display = 'none'; } /** * Table を展開する */ function unfold_table(id) { var table = $(id) var tbody = table.getElementsByTagName('tbody')[0]; tbody.style.display = ''; }
今回は直接スタイルをいじっていますが、実際はクラスをいじったほうがいいでしょう。テーブルタグは以下のような感じで作ります。
<table id="hoge"> <thead> <tr><th>ヘッダ</th></tr> <thead> <tbody> <tr><td>ボディ</td></tr> </tbody> <tfoot> <tr><td>フッタ</td></tr> </tfoot> </table>
呼び出しは、
javascript:fold_table('hoge');
となります。
WT-Toolkit コードリーディング(1)
使ってみて気になる点
連休中、WT-Toolkitを使って遊んでいました。ドキュメントを読みながらいくつかサンプルを作成していたのですが、挙動についていくつか気になったので調べてみることにしました。気になったのは以下の点です。
wtBarChart クラスをハック?してみる
グラフの描画のタイミングを調べるために、ライブラリのコード内部に alert() を追加しながら挙動を調べてみました。すると、結構不思議な事が分かりました。以下は、wtBarChartクラスに関しての結果です。
グラフ描画のタイミング
グラフは、基本的に、プロット点とグラフ領域(意味あってる?)からなるのですが、wtToolkitでは、
グラフを表示する領域(背景面など)は、コンストラクタが呼ばれた際に描画されているようです。
コンストラクタでは、このほかにフィールドの初期化をしています。コンストラクタでは、表示するデータは与えないので、バーについての描画はコンストラクタでは行われません。
グラフ描画の方法
グラフは、いくつかの矩形を組み合わせて作成されています。例えば、3Dグラフの描画領域は、「側面」、「底面」、「背景」の3つから面を組み合わせて作成します。基本となる矩形を作成し、それを回転、ゆがみなどを加えて立体的にしています。
同様に、wtBarChart のバー (wtBar) も、3つの矩形を組み合わせて作られています。さらにコードを追っかけてみると、
というように、結構綺麗な継承がなされていることが分かりました。SVGでも、複数のPathを組み合わせてグループ化してました (^^;)
内部変数が多く、setter、getterがない
JavaScript なんだから関係ねーよと思うかも知れませんが、内部変数が多く、それらへの変更等のアクセサが用意されていません。
特に、1本のバーの幅などは barWidth という変数に保持されていました。デフォルト値はなぜか 30 が設定されています。
さらに、バーと次のバーの間の幅と思われる barSpace については、間隔ではなく、バーの右下基準点から次のバーの右下基準点までの距離らしいです。(実際、この値を 0 にしたところ、全てのバーが重なってしまいました。)
他にも、透明度の初期値などもマジックナンバーが埋め込まれており、ちょっと分かりづらいです。
色が毎回異なる件
表示する度(F5を押す)色が変わる原因は、コンストラクタでグラフの色をジェネレータを使って生成しているからのようです。ただ、全てのバーを同じ色にしたかったり、自分の好みの色が使えないのもなんかいやです。
addBar(30, "White") # 値が30の白のバーを追加 とか addBar(40, "255,255,255") # 同上
とかできたらいいですよねー。さらに欲を言えば、
setBar(1, 40) # 1番目のバーの値を 40 にして再描画 とか setBarColor(1, "Pink") # 1番目のバーの色を Pink にして再描画
とかあるといいですねー。
再描画って強調してるのは、このグラフ、addBar したときに SVG が生成されるらしく、その後の変更ができないからです。
setBarValue(_index, _value, _color) ってのはあるんですけど、どうやら、値のラベルを変更するだけで、長さは変わりませんでした。(´・ω・`)
簡単にまとめ
- wtToolkit のグラフ描画はコンストラクタで背景が描画される
- プロット点の描画は、要素の追加の際に行われる
- グラフはカラージェネレータで毎回色が変わる
- グラフは静的なもので変更に弱い
- 継承関係とかは割と好き♥
続きます・・・多分
WT-Toolkit のグラフをIEでも表示できるようにする
IEでは動かないんですよ
前回の日記では、JavaScriptベースのグラフィックライブラリ WT-Toolkitを用いたグラフ作成のサンプルを紹介しました。
しかし、このサンプル、実はFirefox 2.0 以上でないと見れないはずです(Firefox のヴァージョンはうろ覚え)。少なくとも Internet Explorer では見れないはずです。実際、僕のマシンのIE 6.0 では、以下のようなエラーがでます。
エラー:予期しないメソッドの呼び出し、またはプロパティ アクセスです。
WT-Toolkit が生成するもの
なぜ、Firefox ではグラフが表示されて、IE では表示されないんでしょう?
鋭い人なら原因はすぐに分かると思います。
結論から言ってしまえば、WT-Toolkit は、グラフを SVG [Scalable Vector Graphics] 形式で出力しているので、IEではデフォルトでは動作しません。
Firefox ユーザの方で、Firebug (高機能なデバッガ) を使ったことがあるのなら、グラフのHTMLを見てみてください。以下のような感じのデータが出力されているはずです。
<svg id="1" style="position: absolute;">
<g id="1" transform="translate(0) />
<text id="1" style="position: absolute;">Chart Title</text>
<g id="1" transform="translate(31.2132, 180)"></g>
<text id="1" style="position: absolute;">40</text>
</g>
</svg>
Firefox の SVG と IE の VML
Firefox はネイティブで SVG をサポートしてくれています。それに対して、IEでは、SVGではなく、VML [Vector Markup Language] をサポートし、SVGには対応していません。一応、SVGが主流なので、IEは早めにSVGのネイティブサポートして欲しいですね。(^^;
SVG を VML に渡して解決します
動いてるものを見る
では、IEでSVGを表示するようにサンプルを改良します。WT-Toolkit の公式サイトの Live Demo では、 IE でも問題なくグラフが表示されています。そこで、Live Demo のソースコードをちょっと拝借して、サンプルとの違いを探してみました。
どうやら、IE で SVG 形式のデータを表示するには、SVGのタグの処理を VML に代替させることで表示できるみたいです。
XMLNamespace を追加する
SVG の処理を VML に任せるためには、SVG、VML等の XMLNameSpace を追加し、SVG 用のタグの振舞いをVMLで行わせるようにすることで、IEでもSVGを表示しているように見せることができます。
まず、HTMLタグにXMLNameSpaceを追加します。XMLNameSpace そのものの説明については、適宜、ググって下さい。
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
これで、svg, v, o タグの名前空間が宣言されました。次に、v, o タグの振舞いをVMLに任せるようにします。HEADタグ内に書いてください。
<style type="text/css">
v\:* { behavior: url(#default#VML);}
o\:* { behavior: url(#default#VML);}
</style>実際は、style の指定はなくても表示されませんでしたが、念のために書いた方がいいです。
WT-Toolkit で遊んでみた
本業(学生)が忙しくて気がつけばかなりのスパンが開いてしまいました。学会発表も一段落したのでまたぼちぼちと書いていってみたいと思います。
WT-Toolkit ってなに?
WT-Toolkit は、JavaScript を利用したグラフィックライブラリです。棒グラフ(BarChart)、円グラフ(PieChart)、線グラフ(LineChart) などの基本的な統計グラフが用意されていて、ちょっとした可視化にはもってこいのライブラリです。
便利そうだけど、サンプルがあまり見当たらない
インストールをしてみたものの、ディレクトリにはサンプルが収録されていません。公式サイトの Live Demo には、かなりナウいグラフ描画のデモがあるのですが、ソースコードが圧縮されているため、まともに読むことはできません。(読めるけど変数名が _69 とかになってる)
導入
まずは、公式サイトから WT-Toolkit パッケージをダウンロードします。
公式サイト: http://wt-toolkit.sourceforge.net/
ページの [Downloads] からいけます。
ダウンロードが完了したら、適当なところに展開します。展開したディレクトリを見てみると、中に色々あると思いますが、重要なのは
- doc ・・・ API ドキュメント
- js ・・・ ライブラリ本体
の2点です。
基本的に、WT-Toolkit を使う場合は、適当な箇所にjs ディレクトリを用意するといいみたいです。
棒グラフを表示してみる new wtBarChart
では、WT-Toolkit を使って棒グラフを表示したいと思います。
作業環境の準備
まず、作業用のディレクトリを作り、そこに、js ディレクトリをまるごとコピーします。そして、描画用のテストHTMLを作ります。以下の例では、作業用のディレクトリを work としています。
work ┬ js ・・・ ライブラリが格納されたディレクトリ └ barChart.html ・・・ これに描画用のスクリプトなんかを書いていきます。
HTMLの雛形の作成とWT-Toolkit の読込
とりあえず、最低限度のHTMLだけは書きましょう。今回は、描画用のスクリプトをHTML内部に書くので、HEADタグとかは忘れずに。雛形ができたら、WT-Toolkit のライブラリを読み込むようにします。読み込むファイルは、js/wt_core.js だけでいいです。
WT-Toolkit の読込
語尾の ?tx=ab については、良く分かりません。今日はじめたものですので。(^^;
これで雛形とライブラリの読込が完了しました。
グラフ描画領域の作成
次に、body タグ内に、グラフを表示するための領域を確保します。DIVタグで描画領域を確保します。
描画用の領域を確保 <div id="barChart" width="500px" height="500px"></div>
上記の例では、縦 500px、横 500px を確保しています。
id要素を含めて忘れずに指定してください。
描画用関数の用意
グラフを表示するための領域も確保できたので、いよいよ、描画のためのスクリプトを書いていきます。まず、棒グラフの作成を行うためのメソッドを用意します。そして、この関数を、ページ読込時に呼出されるようにします。
onLoad に呼出すのは、表示用の領域が読み込まれる前に挿入しようとしてエラーになるのを防ぐためです。
そして、rendarBarChart() 内に棒グラフを作成する処理を書いていきます。
グラフ作成の前に・・・
グラフ作成の前に、WT-Toolkit の棒グラフ用のライブラリを見てみましょう。棒グラフは wtBarChartというクラスで定義されています。詳しくは、API ドキュメントを読んでください。
今回は作成する上で最低限必要な、
- コンストラクタ
- データの追加
だけ説明します。
まず、コンストラクタは以下のようになっています。
wtBarChart(_parent, _x, _y, _type, _width, _height, _depth, _angle, _xMin, _xMax, _yMin, _yMax)
表に整理してみます。
| 引数 | 型 | 意味 |
|---|---|---|
| _parent | wtCanvas クラスか wtVectorGroup | グラフを描画するためのオブジェクト |
| _x | 数値 | 基準X座標? 0 が無難 |
| _y | 数値 | 基準Y座標? 0 が無難 |
| _type | 文字列 | グラフの向き "vertical" か "horizontal" を指定 |
| _width | 数値 | グラフの幅 |
| _height | 数値 | グラフの高さ |
| _depth | 数値 | グラフの奥行き |
| _angle | 数値 | 歪み 45度くらいがベスト |
| _xMin | 数値 | X軸の最小値 |
| _xMax | 数値 | X軸の最大値 |
| _yMin | 数値 | Y軸の最小値 |
| _yMax | 数値 | Y軸の最大値 |
棒グラフを作成したい場合は、このコンストラクタを呼べば空の棒グラフが作成されます。
wtCanvas と wtVectorGroup
コンストラクタの引数のほとんどは数値と文字列ですが、1つだけそうじゃないのがあります。それは_parent です。この引数にはそれぞれ、wtCanvasクラスのインスタンスか、wtVectorGroupのインスタンスを与えてやります。今回は、wtCanvasクラスのインスタンスを与えることにします。
まず、wtCanvas のコンストラクタを呼出します。wtCanvas のコンストラクタは、以下の引数をとります。
wtCanvas(
| 引数 | 型 | 意味 |
|---|---|---|
| parentWidget | wtWidget クラスか DOM_Node クラス | canvasを埋め込む要素 Element |
| width | 数値 | canvas (描画領域)の幅 |
| height | 数値 | canvas の高さ |
以下が呼出し例になります。
var element = document.getElemenyById('canvas'); // id 属性が canvas の要素を得る
var canvas = new wtCanvas(element, 500, 500); // 500 × 500 のcanvas領域を確保
上記の例では、だいぶ前に定義した id が "canvas" の DIV タグを指定しています。
この canvas を wtBarChart のコンストラクタに与えてやればよいのです。
棒グラフの作成とデータの追加
前置きがながくなりましたが、これで終わりです。
先ほど作成した wtCanvas オブジェクトを引数に、棒グラフを作成します。
var barChart = new wtBarChart(canvas, 0, 0, "vertical", 300, 300, 1, 45, 0,100, 0, 100);
これで空の棒グラフが作成されました。後は、このグラフに表示したいデータを追加してやれば言いだけです。
データの追加には、addBar メソッドを使います。面倒なので使い方だけ。
barChart.addBar(50); // 値が50のデータを追加
データを配列で用意している場合はこんな感じですね。
var data = [10, 20, 30, 40, 50];
for ( var i = 0; i < data.length; i++ ) {
barChart.addBar(data[i]);
}
Calender Helper Plugin をいじってみる(2)
前回の日記ではCalendar Helper Plugin を日本語化してみようとがんばりました。とりあえず、表示形式をオプションで渡して処理をさせることにしました。今回は、ソースコードをいじってみます。
※ 注意: 初心者が修正したものなのでしょぼいかもしれませんよ
オリジナルソースコードを読んでみる
とりあえず、オリジナルのソースがどうなっているのか調べてみます。オリジナルのソースは、
railsapp/vendor/plugins/calendar_helper/lib
にあります。
エディタで開いて、少し読んでみたところ、以下の部分がHTML出力に関係ありそうです。
76: day_names = Date::DAYNAMES.dup
77: first_weekday.times do
78: day_names.push(day_names.shift)
79: end
80:
81: cal = %(<table class="#{options[:table_class]}"
border="0" cellspacing="0" cellpadding="0">)
82: cal << %(<thead><tr class="#{options[:month_name_class]}">
<th colspan="7">#{Date::MONTHNAMES[options[:month]]}</th></tr>
<tr class="#{options[:day_name_class]}">)
83: day_names.each {|d| cal << "<th>#{d[options[:abbrev]]}</th>"}
84: cal << "</tr></thead><tbody><tr>"
85: beginning_of_week(first, first_weekday).upto(first - 1) do |d|
86: cal << %(<td class="#{options[:other_month_class]})
87: cal << " weekendDay" if weekend?(d)
88: cal << %(">#{d.day}</td>)
89: end unless first.wday == first_weekday
90: first.upto(last) do |cur|
91: cell_text, cell_attrs = block.call(cur)
92: cell_text ||= cur.mday
93: cell_attrs ||= {:class => options[:day_class]}
94: cell_attrs[:class] += " weekendDay" if [0, 6].include?(cur.wday)
95: cell_attrs = cell_attrs.map {|k, v| %(#{k}="#{v}") }.join(" ")
96: cal << "<td #{cell_attrs}>#{cell_text}</td>"
97: cal << "</tr><tr>" if cur.wday == last_weekday
98: end
99: (last + 1).upto(beginning_of_week(last + 7, first_weekday) - 1) do |d|
100: cal << %(<td class="#{options[:other_month_class]})
101: cal << " weekendDay" if weekend?(d)
102: cal << %(">#{d.day}</td>)
103: end unless last.wday == last_weekday
104: cal << "</tr></tbody></table>"
処理を整理してみる
詳しく読んでみたら、大体こんな感じで処理をやっています。
- 76行目で曜日(Suday, Monday)を配列で取得
- 77-79行目で、指定した曜日が週の先頭に来るようにpush、shiftを繰り返す
- 81行目でtableタグを出力
- 82行目で該当月を出力(Date::MONTHNAMES で January とかを取得)
- 83行目で曜日を出力
- 後は、まぁ、がんばって
日本語化をするためには、上記の行で扱うデータを日本語のものにすり替えてしまえばいいですよね?日本語データとして必要なのは、
- 月の名前(1月などの通常のもの、睦月などの陰暦)
- 曜日の名前(日、月、火といった感じ)
ですね。
というわけでこれらを定数として定義します。
ソースコードの修正(追加も)
##---- 通常の日本語月
JAPANESE_NORMAL = ["", "1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"]
##---- 陰暦
JAPANESE_LUNAR = ["", "睦月", "如月", "弥生", "卯月", "皐月", "水無月",
"文月", "葉月", "長月", "神無月", "霜月", "師走"]
##---- 曜日
JAPANESE_WEEK = ["日", "月", "火", "水", "木", "金", "土"]定数名は適当です。気に入らなかったら変えてくださいな。で、後は前回の日記で指定した :options の値によってこれらの情報とすり替えてやればOKです。
月と曜日のすり替えはこんな感じです。
##---- 月の表示
month = options[:month]
case options[:month_expression]
when 'jp_normal'
month_name = JAPANESE_NORMAL[month]
when 'jp_lunar'
month_name = JAPANESE_LUNAR[month]
else
month_name = Date::MONTHNAMES[month]
end
##---- 曜日らしい([Sunday, Monday, Tuesday, ... Saturday])
case options[:week_expression]
when 'jp'
day_names = JAPANESE_WEEK
else
day_names = Date::DAYNAMES.dup
end
first_weekday.times do
day_names.push(day_names.shift)
endで、出力部分も少しいじります。
82行目の
cal << %(<thead><tr class="#{options[:month_name_class]}">
<th colspan="7">#{Date::MONTHNAMES[options[:month]]}</th></tr>
<tr class="#{options[:day_name_class]}">)
を
cal << %(<thead><tr class="#{options[:month_name_class]}">
<th colspan="7">#{month_name}</th></tr>
<tr class="#{options[:day_name_class]}">)単に直接 Date::MONTHNAMESってやってたのをmonth_nameに変えただけです。
動作確認
修正できたら、サーバを再起動して正しくできたか修正できたかを確認します。
カレンダーを埋め込みたいrhtmlにコードを書きます。
月を陰暦で、曜日を日本語で表示
<%=
calendar({:year => 2007, :month => 6, :month_expression => 'jp_lunar', :week_expression => 'jp'}) do |d|
cell_text = "#{d.mday}<br/>"
cell_attrs = {:class => 'hoge'}
[cell_text, cell_attrs]
end
%>
月を1月2月で、曜日を英語で表示
<%=
calendar({:year => 2007, :month => 6, :month_expression => 'jp_normal'}) do |d|
cell_text = "#{d.mday}<br/>"
cell_attrs = {:class => 'hoge'}
[cell_text, cell_attrs]
end
%>実行結果は、それぞれ以下のようになりました。
以上でカレンダープラグインの導入と似非日本語かも終わりました。
Calendar Helper Plugin をいじる
昨日の日記でRailsで使えそうなカレンダープラグインを列挙しました。それぞれのドキュメントを読んだりした感じから、一番シンプルで拡張しやすそうだという理由から、Calendar Helper Plugin を使うことにしました。
Calendar Helper Plugin のおさらい
概要
昨日は眠かったのであまり上手くまとめられていなかったので、もう一度簡単にまとめてみます。
Calendar Helper Plugin は、Jeremy Voorhis さんが作って、Jarkko Laine さんが拡張したプラグインです。TABLEタグで作られたシンプルなカレンダーを作成してくれます。
特徴
Calendar Helper Plugin は、ビューに対応するrhtmlファイルの挿入したい箇所にコードを挿入すると、そこに生成されたHTMLが埋め込まれます。基本的な使い方は以下のような感じです。
app/views/hoge.rhtml <%= calendar(:year => 2007, :month => 6) %>
calendar は、呼出し時に少なくともオプションとして、表示する年(;year)と月(:month)を指定する必要があります。他にも色々なオプションがありますが、詳しくはドキュメントを読んでみてください。
Plugin の拡張
というわけで、対応していないなら対応させてしまえということに。まず、どんな出力をサポートするかを考えます。とりあえず、月 => 1月、曜日 => 日といった一般的な日本語はサポートしましょう。これだけだとつまらないので、月に陰暦も使えるようにしてみます。(睦月とか卯月ってやつです)
オプションで渡してしまえ
さて、インタフェースを変えずに日本語サポートするにはどうしたらいいかと考えた結果、オプションに月や曜日のフォーマットを指定するようにすればいいことにしました。オプションはとりあえず以下のような名前にしました。
- 月の表示に関するオプションは :month_expression
- 曜日の表示に関するオプションは :week_expression
英語がアレなのでまずかったら言ってください。これに識別子を与えてやります。月の表示方法には以下の2つの識別子をあげましょう。
- 通常の月(1月、2月)は 'jp_normal'
- 陰暦(睦月、卯月)は 'jp_lunar'
これもアレだったら読み替えてください。
曜日は今のところ日、月、火の簡略方式のみにしましょう。長すぎるとカレンダーがコンパクトになりませんしね。
- 曜日を日本語にする場合は 'jp'
これを使った呼び出し例はこんな感じになります。
月を陰暦で、曜日を日本語で表示する
<%= calendar({:year => 2007, :month => 6,
:month_expression => 'jp_lunar', :week_expression => 'jp'}) %>
これで一応インタフェース部分は終了です。
コード修正は長くなりそうなのでまた明日。
Ruby on Rails に組み込めるカレンダープラグイン+α リスト(仮)
バイトで、Railsに組み込めるプラグインを調べてからプログラムを書いてねと言われたので調査。Todoリストとか予定表に使うカレンダーに関するRails用のプラグインを探してみた。
Calendar Helper Plugin
概要
MITライセンスのカレンダープラグイン。rhtmlに埋め込んで使う。
URL: http://wiki.rubyonrails.org/rails/pages/Calendar+Helper+Plugin
svn: http://topfunky.net/svn/plugins/calendar_helper
インストール
railsapp>ruby script/plugin install http://topfunky.net/svn/plugins/calendar_helper railsapp/vendor/plugins/calendar_helper>rake rdoc railsapp>ruby script/generate calendar_styles
使い方
カレンダーを挿入したいrhtmlファイルに以下のコードを記述する。<%=calendar(:year => 2005, :month => 6)%>
オプション :year, :month は必ず指定しなければいけない。
その他のオプションで使えそうなものとしては、 :first_day_of_week がある。
これは、カレンダーの週の初めを何曜日で固定するかを示す。
日曜日が 0、月曜日が 1 といったように対応する。
機能的な特徴
日ごとに分解して処理できる。1日ごとになんらかの処理を行いたい場合は、
calendar(:year => 2007, :month => 06) do |d|
[d.mday, {:class => 'hoge'}]
end
とすればよい。
- d は Date オブジェクト
- イテレータでは、配列 [表示文字、TDタグの属性値] を返さなければいけない
- 属性値は、ハッシュで表現する。 {:class => 'hoge'} →
- :class の指定がないとエラーが発生する。
Datetime Toolbocks plugin
Yahoo! UI Library: Calendar
とりあえず Calendar Helper
Railsでカレンダーのプラグインってあまりなかったです。とりあえず、一番使いやすそうと落ち着いたのが、最初に紹介した Calendar Helper です。
ただ、これもちょっと難点があります。外国の方が作ったものなので、カレンダーは英語版のみ。
1月とか火曜日とか日本語で表示できないのです。とりあえず、明日は Calendar Helper を改造します。



