巷では 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は下位互換性についてはかなり善処している方だと思いますが,
やっぱり未だに移行できない部分というのもあるもんです。