2008年3月14日金曜日

Java でタグクラウドの実装

Java でタグクラウドの実装をする必要があったので、ググってみましたがなかなかピンとくる情報に出会えなかったため、ネタにしてみます。

幸いにも計算部分は Perl の実装を参考にさせていただくことができたので、Java 風のアレンジを加えてみました。

まずタグのインタフェースを定義します。Comparable を継承しているのはソートに使用するためです。
public interface Tag extends Comparable {
/** タグそのものを返します */
String getValue();
/** タグの使用回数など指標になる数値を返します */
int getScore();
/** タグクラウドの文字の大きさを返します */
int getCloudLevel();
/** タグクラウドの文字の大きさを設定します */
void setCloudLevel(int cloudLevel);
}

次にインタフェース Tag の実装クラスです。
import java.io.Serializable;
public class ExampleTag implements Tag, Serializable {
private String value;
private int score;
private int cloudLevel;
public ExampleTag(String value, int score) {
this.value = value;
this.score = score;
}
public String getValue() {
return value;
}
public int getScore() {
return score;
}
public int getCloudLevel() {
return cloudLevel;
}
public void setCloudLevel(int cloudLevel) {
this.cloudLevel = cloudLevel;
}
public int compareTo(Object o) {
Tag tag = (Tag) o;
return (getScore() - tag.getScore());
}
public String toString() {
return "ExampleTag[value=" + value + ",score=" + score
+ ",cloudLevel=" + cloudLevel + "]";
}
}

最後にタグクラウドの実装です。main にテスト用のコードを書いておきます。TagCloud クラスを作成して Tag オブジェクトを addTag していって最後に getTags で取り出して使います。順序を維持するために java.util.LinkedHashSet を使っています。
import java.util.Iterator;
import java.util.Set;
public class TagCloud {
/** フォントの大きさの段階を指定 */
private final int maxLevel;
/** タグを順序どおりに格納する Set */
private final Set<Tag> tags;
/** 計算済みフラグ */
private boolean clouded;
/** 最小 */
private int minScore = Integer.MAX_VALUE;
/** 最大 */
private int maxScore = Integer.MIN_VALUE;
/** コンストラクタ */
public TagCloud(int maxLevel) {
this(maxLevel, 16);
}
/** コンストラクタ */
public TagCloud(int maxLevel, int capacity) {
if (maxLevel < 1) {
throw new IllegalArgumentException("invalid maxLevel " + maxLevel);
}
this.maxLevel = maxLevel;
this.tags = new java.util.LinkedHashSet<Tag>(capacity);
}
/** タグを追加します */
public void addTag(Tag tag) {
if (tag != null) {
tags.add(tag);
int score = tag.getScore();
this.minScore = Math.min(minScore, score);
this.maxScore = Math.max(maxScore, score);
this.clouded = false;
}
}
/** タグの Set を返します */
public Set getTags() {
cloud();
return tags;
}
/** タグクラウドのための計算をします */
private void cloud() {
final int size = tags.size();
if (clouded || size == 0) {
return;
}
if (size == 1) {
Tag tag = tags.iterator().next();
tag.setCloudLevel(1);
} else {
double min = (minScore == 0) ? 0.0: Math.log(minScore);
double max = (maxScore == 0) ? 0.0: Math.log(maxScore);
double f;
if (min == max) {
min -= maxLevel;
f = 1.0;
} else {
f = maxLevel / (max - min);
}
if (size < maxLevel) {
f *= ((double) size / maxLevel);
}
for (Tag tag: tags) {
int score = tag.getScore();
if (score != 0) {
tag.setCloudLevel((int) ((Math.log(score) - min) * f));
}
}
}
this.clouded = true;
}
// NOTE: for test
public static void main(String[] args) {
TagCloud tagCloud = new TagCloud(4);
tagCloud.addTag(new ExampleTag("abcd", 3));
tagCloud.addTag(new ExampleTag("efg", 1));
tagCloud.addTag(new ExampleTag("hijk", 2));
tagCloud.addTag(new ExampleTag("lmn", 10));
tagCloud.addTag(new ExampleTag("opqrstu", 7));
tagCloud.addTag(new ExampleTag("vwxyz", 5));
System.out.println(tagCloud.getTags());
}
}

上記 3つのソースを同じディレクトリにおいて javac *.java でコンパイル、java TagCloud でテスト実行すると以下のような結果が出力されます。
[ExampleTag[value=abcd,score=3,cloudLevel=1],
ExampleTag[value=efg,score=1,cloudLevel=0],
ExampleTag[value=hijk,score=2,cloudLevel=1],
ExampleTag[value=lmn,score=10,cloudLevel=3],
ExampleTag[value=opqrstu,score=7,cloudLevel=3],
ExampleTag[value=vwxyz,score=5,cloudLevel=2]]

このクラスを利用して JSP などで HTML 出力すれば
<style type="text/css">
.tagcloud {width:180px;}
.tagcloud span {padding:5px; font-weight:bold; color:steelblue;}
.tagcloud .tag0 {font-size:10px;}
.tagcloud .tag1 {font-size:16px;}
.tagcloud .tag2 {font-size:22px;}
.tagcloud .tag3 {font-size:28px;}
</style>
<div class="tagcloud">
<span class="tag1">abcd</span>
<span class="tag0">efg</span>
<span class="tag1">hijk</span>
<span class="tag3">lmn</span>
<span class="tag3">opqrstu</span>
<span class="tag2">vwxyz</span>
</div>


こんな感じのタグクラウドになります。

0 件のコメント: