JavaFX でワードクラウドを作る

# 概要
JavaFX でワードクラウドを作ってみます。

# ワードクラウドとは
出現頻度等のスコアで文字の大小を変えるデータビジュアライズ手法の1つです。一般的にはブログのタグクラウドが知られています。

# 背景
JavaFX の WebView + [D3.js|https://d3js.org/] + [D3-cloud.js](https://github.com/jasondavies/d3-cloud) の組み合わせでワードクラウドアプリケーションを作ったところ、レンダリングが終了するまで15秒ほどかかっていました。ハイブリッドアプリをやめて JavaFX だけで実装し直したところ、150ミリ秒で描画できるようになりました。

# スクリーンショット
今回は下記のようなアプリケーションを作ります。

## 起動直後
![Screenshot1.png](https://qiita-image-store.s3.amazonaws.com/0/102004/625b1955-e6c6-ae16-eadf-17a6ca183930.png)
下部の TextArea に文章を入力して、右の描画ボタンを押します。今回は[『レ・ミゼラブル』「第一部　ファンティーヌ」](http://www.aozora.gr.jp/cards/001094/files/42600_25758.html) のテキスト(276,167字)を使ってみます。

## 実行後
![Screenshot2.png](https://qiita-image-store.s3.amazonaws.com/0/102004/8acb2102-2812-e08d-b8e0-bcae550da602.png)

件数に応じてフォントサイズが変化します。ワードにマウスカーソルを当てると件数を参照できます。
形態素でのフィルタをしていないので全然面白くない結果になってしまいました……

# 実装
ある文章からワードクラウドを作るのに必要なのは下記の3ステップです。
1. 文章を形態素解析する
2. 単語をカウントアップする
3. 画面に文字を描画する

それぞれ見ていきましょう。

## 1. 文章を形態素解析する
普通なら [kuromoji](http://www.atilika.org/) や [Mecab](http://mecab.googlecode.com/svn/trunk/mecab/doc/index.html?sess=3f6a4f9896295ef2480fa2482de521f6) や [Sen](https://java.net/projects/sen) を使うところですが、今回は [TinySegmenter](http://chasen.org/~taku/software/TinySegmenter/) という軽量の形態素解析器を Java に移植したものを使います。精度はさほどでありませんが、非常に高速に単語へと分解できます。

## 2. 単語をカウントアップする
1の解析結果を Map&lt;String, Integer&gt; を使ってカウントアップしていきます。

1と2は下記のメソッドでまとめて実行しました。

```java:draw
    public static void draw(final Pane canvas, final String text) throws IOException {
        canvas.getChildren().clear();
        final TinySegmenter ts = TinySegmenter.getInstance();
        final MutableMap<String, Integer> map = Maps.mutable.empty();
        ArrayAdapter.adapt(text.split(System.lineSeparator()))
            .select(str -> {return StringUtils.isNotEmpty(str);})
            .each(  str -> {
                ts.segment(str).stream()// 1
                    // ここから 2.
                    .map(seg     -> {return seg.replace("\"", "").trim();})
                    .filter(seg  -> {return StringUtils.isNotEmpty(seg);})
                    // 1文字だけのものは描画しない.
                    .filter(seg  -> {return seg.length() != 1;})
                    .forEach(seg -> {map.put(seg, map.getIfAbsentValue(seg, 0) + 1);});
        });
        placeTexts(canvas, map);
    }
```

## 3. 画面に文字を描画する
[D3-cloud.js](https://github.com/jasondavies/d3-cloud) では Canvas を使って描画をしていました。今回は簡単に、Label を使って描画します。具体的には、Pane の Children にどんどん Label を add していきます。Pane は FlowPane を使うと割ときれいに描画できました。

文字の色はランダムで、フォントサイズは出現頻度に応じて、それぞれ変えています。

```java:placeTexts
    private static void placeTexts(final Pane canvas, final MutableMap<String, Integer> map) {
        final int max = map.max();
        map.forEach((k, v) -> {
            // 出力対象が多数存在する場合は足切り.
            if (50 < map.size() && v < (max / 20)) {
                return;
            }
            final Text text = new Text(k);
            // 出現頻度に応じてサイズを変更.
            text.setFont(new Font(MAX_FONT_SIZE * ((double) v / (double) max) + MINIMUM_SIZE));
            // 色をランダムで設定.
            text.setFill(Color.color(Math.random(), Math.random(), Math.random()));

            // マウスカーソルを合わせた時に表示する Tooltip.
            final Tooltip tooltip = new Tooltip();
            tooltip.setText(k + ": " + v);
            Tooltip.install(text, tooltip);
            canvas.getChildren().add(text);
        });
    }
```

# 改良点
[d3-cloud.js](https://github.com/jasondavies/d3-cloud) のようにスペースを有効に使った配置を実装する

# まとめ
入力された文章からワードクラウドを生成するアプリケーションができました。勉強会やイベントの参加レポートを作成する際に使うと効果的なビジュアライズができそうです。また、文字の配置を工夫したり、形態素解析部分を変更したりして、よりよいワードクラウドを表示できるように改良してみると面白いかもしれません。

# ソースコード
[GitHub Repository](https://github.com/toastkidjp/word_cloud)