巷では Ajax 云々が大人気です。あの Google Maps も実のところ、Ajax のコアを成す JavaScript を使ってあれだけのことを実現しています。FLASH に頼らずにブラウザだけで完結しているのは本当に驚きですね。JavaScript もまだまだ捨てたものじゃないという優れたお手本だと思います。
さて、記事分類の新たな切り口として先日、本サイトにも導入してみた TagCloud ですが、JavaScript の実力にビックリしたことを糧に、今回、これを JavaScript を用いてパワーアップしてみました。Google Maps には遠く遠く及びもしませんでしたが、その一部を真似てスライダコントロールモドキを導入し、また、タグのソート順を自由に切り替えられるようにもしてみました。
// オリジナル(α) function calcFontSize (count) { return count / 6 + 12; } // 改造後(β) // min count of tag appearance (cf. to retrieve with MTTags) var count_min = <MTTags lastn="1" sort_by="count" sort_order="ascend"><$MTTagCount$></MTTags>; // max count of tag appearance (cf. to retrieve with MTTags) var count_max = <MTTags lastn="1" sort_by="count" sort_order="descend"><$MTTagCount$></MTTags>; function calcFontSize (count) { var size_min = 12; // min font size on count_max var size_max = 28; // max font size on count_min return (size_max - size_min) * (count - count_min) / (count_max - count_min) + size_min; }
カットオフ値による表示・非表示についても、同じ原理で正規化された出現回数を基に判定しています。
今は単純に線形補間としていますが、タグの使用頻度の分布まで考えた方がモア・ベター
onclick
ハンドラで所望の TagCloud を表示し、
それ以外を非表示にしています。単純。
function E (id) { return document.getElementById (id); } function selectCloud (index) { var clouds = ['tags_name', 'tags_date', 'tags_count']; for (var i = 0; i < clouds.length; i++) E(clouds [i]).style.display = 'none'; initCloud (clouds [index]); E(clouds [index]).style.display = 'block'; } : : <input type="button" onclick="selectCloud (0);" value="辞書順"> <input type="button" onclick="selectCloud (1);" value="更新日"> <input type="button" onclick="selectCloud (2);" value="使用頻度">
tags
サブルーチン中に以下の部分を追加します。
my @list; if ($sort_by eq 'tag' || $sort_by eq 'keyword' ) { @list = $sort_order eq 'ascend' ? sort { lc $a cmp lc $b } keys %tags : sort { lc $b cmp lc $a } keys %tags; } elsif ($sort_by eq 'tag-case' || $sort_by eq 'keyword-case') { @list = $sort_order eq 'ascend' ? sort keys %tags : sort reverse keys %tags; ;# piroli++ ↓ここから追加 } elsif ($sort_by eq 'date') { @list = $sort_order eq 'ascend' ? sort { $ts{$a} <=> $ts{$b} } keys %tags : sort { $ts{$b} <=> $ts{$a} } keys %tags; ;# ++piroli ↑ここまで追加 } else { @list = $sort_order eq 'ascend' ? sort { $tags{$a} <=> $tags{$b} } keys %tags : sort { $tags{$b} <=> $tags{$a} } keys %tags; }
これにより
sort_by
オプションで、タグが登録された記事の更新日でソートが行えます。
また、他同様 sort_order
による昇順/降順指定もそのまま有効です。
ブラウザによってイベントハンドラの登録方法が異なるようで、 全てのブラウザについて動作の保証が取れていません。 特にスライダ部分の動作が大きくことなるようです (Netscape7.1…動作、FireFox…動作、IE6.0…一部動作、Opera…全くダメ) うぁ〜…手間だなぁ(´・ω・`)
<script type="text/javascript"> // "JavaScript de Sugoi TagCloud" v.0.90 // Programmed by Piroli YUKARINOMIYA (Open MagicVox) // @see http://www.magicvox.net/archive/2005/09241727.php // Original concept by ogawa (Ogawa::Memoranda) // @see http://as-is.net/blog/archives/001027.html // Division ticks of slider controller var division = 10; // min count of tag appearance (cf. to retrieve with MTTags) var count_min = <MTTags lastn="1" sort_by="count" sort_order="ascend"><$MTTagCount$></MTTags>; // max count of tag appearance (cf. to retrieve with MTTags) var count_max = <MTTags lastn="1" sort_by="count" sort_order="descend"><$MTTagCount$></MTTags>; //////////////////////////////////////////////////////////////////////// function E (id) { return document.getElementById (id); } //////////////////////////////////////////////////////////////////////// // Retrieve the font size by linear compensation // mapping count in [count_min, count_max] -> [size_min, size_max] function calcFontSize (count) { var size_min = 12; // min font size on count_max var size_max = 28; // max font size on count_min return (size_max - size_min) * (count - count_min) / (count_max - count_min) + size_min; } // Retrieve the normalized count of tag appearance // mapping count in [count_min, count_max] -> [1, division] function calcNormalizeCount (count) { return division * (count - count_min) / (count_max - count_min) + 1; } // Retrieve style by freshness function calcRefreshTime (diff) { if (diff <= 3) return 'diff1'; if (diff <= 10) return 'diff2'; if (90 <= diff) return 'diff4'; if (30 <= diff) return 'diff3'; return null; } // Initialize the tag cloud named 'name' var tags = null; function initCloud (name) { tags = new Array(); var tagsNode = E(name); var childNodes = tagsNode.childNodes; var now = (new Date()).getTime(); for (var i = 0; i < childNodes.length; i++) { var e = childNodes.item (i); if (e.nodeName.match (/li/i)) { var s = e.title.split (':'); e.style.fontSize = calcFontSize (s[1]) + 'px'; var d = s[2].split ('-'); var diff = (now - (new Date(d[0], d[1] - 1, d[2])).getTime()) / 86400000; var style = calcRefreshTime (diff); if (style) e.className = style; tags.push ([ e, calcNormalizeCount (s[1]) ]); } } } // Filter tags by coff value var coff = 0; // cut-off value (memo. set his initial value here) function refreshCoff () { if (tags) for (var i = 0; i < tags.length; i++) { var tag = tags[i]; tag[0].style.visibility = tag[1] <= coff ? 'hidden' : 'visible'; } } //////////////////////////////////////////////////////////////////////// // Slider control like Gooooooogle function getSliderPos (tick) { return tick * 8 + 21; } function alignSlider (pos_y) { return parseInt ((pos_y - 21) / 8 + 0.5); } function limitSlider (pos_y) { var limit_min = getSliderPos (0); var limit_max = getSliderPos (division); return pos_y < limit_min ? limit_min : (limit_max < pos_y ? limit_max : pos_y); } function moveSlider (tick) { coff = tick < 0 ? 0 : (division < tick ? division : tick); E('ctrl-minus').style.marginTop = (division * 8 + 36) + 'px'; E('ctrl-slider').style.marginTop = getSliderPos (coff) + 'px'; refreshCoff (); } function initSlider () { // Show slider controller var parts = '/image/ctrl/'; // directory path of slider images document.write ('<div id="controller">'); document.write ('<div id="shadow"><img src="'+parts+'dslidertopshadow.png"><br />'); for (var i = 1; i < division; i++) document.write ('<img src="'+parts+'dsliderbarshadow.png"><br />'); document.write ('<img src="'+parts+'dsliderbottomshadow.png"></div>'); document.write ('<div id="base"><img src="'+parts+'dslidertop.png"><br />'); for (var i = 1; i < division; i++) document.write ('<img src="'+parts+'dsliderbar.png"><br />'); document.write ('<img src="'+parts+'dsliderbottom.png"></div>'); document.write ('<img id="ctrl-plus" src="'+parts+'zoom-plus.png">'); document.write ('<img id="ctrl-minus" src="'+parts+'zoom-minus.png">'); document.write ('<img id="ctrl-slider" src="'+parts+'slider.png">'); document.write ('</div>'); moveSlider (coff); // Event handling of inc/dec buttons E("ctrl-plus").onclick = function () { moveSlider (coff - 1); } E("ctrl-minus").onclick = function () { moveSlider (coff + 1); } // Event handling of clicking the slider base E("base").onclick = function (e) { moveSlider (alignSlider (e.layerY - 6)); } // Event handling of D&D of slider // '06/03/04, pirolix, 諦めたorz var grabObj = null; var slider = E("ctrl-slider"); slider.onmousedown = function (e) {} slider.onmousemove = function (e) {} slider.onmouseup = function (e) {} } //////////////////////////////////////////////////////////////////////// function selectCloud (index) { // see <ul id="...">s below var clouds = ['tags_name', 'tags_date', 'tags_count']; for (var i = 0; i < clouds.length; i++) E(clouds [i]).style.display = 'none'; initCloud (clouds [index]); E(clouds [index]).style.display = 'block'; refreshCoff (); } </script> <style type="text/css"> ul.tags { margin: 0px; margin-left: 30px; padding: 0px; display: none; /* hidden at first */ } .tags a { text-decoration: none; } .tags li { display: inline; padding: 4px; word-break: keep-all; } .tags li.diff1 a { color: #f50; } .tags li.diff2 a { color: #900; } .tags li a { color: #000; } .tags li.diff3 a { color: #77a; } .tags li.diff4 a { color: #bbf; } #controller { margin-left: 4px; } #controller #shadow { position: absolute; } #controller #base { position: absolute; cursor: pointer; } #controller #ctrl-plus { position: absolute; padding-left: 2px; margin-top: 0px; cursor: pointer; } #controller #ctrl-minus { position: absolute; padding-left: 2px; margin-top: 100px; cursor: pointer; } #controller #ctrl-slider { position: absolute; padding-left: 1px; margin-top: 0px; cursor: pointer; } </style> <p> タグを <input type="button" class="ctrl_button" onclick="selectCloud (0);" value="辞書順"> <input type="button" class="ctrl_button" onclick="selectCloud (1);" value="更新日"> <input type="button" class="ctrl_button" onclick="selectCloud (2);" value="使用頻度"> で並べ替える </p> <div id="tag_cloud"> <script type="text/javascript">initSlider ();</script> <ul class="tags" id="tags_name"> <MTTags sort_by="tag" sort_order="ascend"> <li title="<$MTTag$>:<$MTTagCount$>:<$MTTagDate format="%Y-%m-%d"$>"><a href="/tag/<$MTTag encode_url="1"$>" title="<$MTTag$> [<$MTTagCount$>] (<$MTTagDate format="%Y/%m/%d"$>)"><$MTTag$></a></li></MTTags> </ul> <ul class="tags" id="tags_date"><MTTags sort_by="date" sort_order="descend"> <li title="<$MTTag$>:<$MTTagCount$>:<$MTTagDate format="%Y-%m-%d"$>"><a href="/tag/<$MTTag encode_url="1"$>" title="<$MTTag$> [<$MTTagCount$>] (<$MTTagDate format="%Y/%m/%d"$>)"><$MTTag$></a></li></MTTags> </ul> <ul class="tags" id="tags_count"> <MTTags sort_by="count" sort_order="descend"> <li title="<$MTTag$>:<$MTTagCount$>:<$MTTagDate format="%Y-%m-%d"$>"><a href="/tag/<$MTTag encode_url="1"$>" title="<$MTTag$> [<$MTTagCount$>] (<$MTTagDate format="%Y/%m/%d"$>)"><$MTTag$></a></li></MTTags> </ul> <script type="text/javascript">selectCloud (0);</script> </div><!--tag_cloud-->
JavaScript の後半部分はスライダコントロール関連のコードです。 IEとOperaでは一部(または全部)で不具合が出ているので修正の必要があります ('05/09/26 追記)
寄せられたコメント (全 7 件中、最新 5 件まで表示しています)
ファイルを早速ダウンロードしてテストしてみましたが、今度はテンプレートのエラーとなりました。
ただ、エラー表示された内容のようなタグが無いのに表示され…。
いただいたファイルの中身を見てみたのですが、かなり改造されているんですね。
プログラマでは無いしがないWeb屋の私には中々解読が手強そうです(^^;
とりあえず、何とかテンプレートを修正して使用させていただきたいと思います。
TagwireプラグインはとりあえずMT4.x系でも騙し騙し(?)使えているっぽいので,テンプレートの書き方の問題のような気がします>エラー
現在,このサイトで使っているTagwireプラグインを置いておきますので参考にしてください。
http://www.magicvox.net/cgi-bin/mt/plugins/tagwire/tagwire.pl
MTのバージョンは3.2です。
やはりこれだけ古いバージョンだと厳しいんでしょうかね…。
事情によりバージョンUpができないので、何とか対応できたらと考えているのですが。
更新日でソート改造を行ってみたのですが、下記のようなエラーが発生してしまい、再構築ができませんでした。
Undefined subroutine &MT::Plugin::Tagwire::entries called at lib/MT/Builder.pm line 159.
非常に面倒なテンプレートにしてしまっているために、何とか更新日でソートをしたいと考えているのですが、エラーの原因・回避策をご教授いただけないでしょうか…。
説明文はMT標準のタグ機能に倣うように書いているんですが,
タグ機能はOgawa::memorandaのTagwireプラグインを未だに使っているために,
説明と実際が違うことがよくあるんですよ…(直せよ
MTは下位互換性についてはかなり善処している方だと思いますが,
やっぱり未だに移行できない部分というのもあるもんです。