とある母集団文書に含まれる単語頻度を可視化する目的で、タグクラウドをブラウザ上でJavaScriptで作成できないかを調べました。

タグクラウド(ワードクラウド)とは

WikiPedia には以下のように記載されています。

タグ・クラウド (tag cloud、 ワードクラウド 、またはビジュアルデザインの重み付きリスト )はタグの視覚的記述を指す。テキストデータの斬新な視覚表現であり、通常はWebサイトでキーワードメタデータ(タグ)を描写したり、自由形式のテキストを視覚化するが、通常タグは単一の単語であり、各タグの重要性はフォントサイズまたは色で示される。

以下のようなやつです(画像もWikiPediaから拝借しました)。
文字の大きさや色から単語の重要度を視覚的に捉えることができます。

undefined

候補ライブラリ

ある程度見た目が綺麗で、人気がありそうな物をピックアップしました。

正直、現時点では特に複雑な要件もないですし、どれでも良さそうです。
なんとなく見た目が気に入ったamChartsを試してみます。

amChartsを試してみる

  • index.htmlを作る(DEMOコードを貼り付けて少し整理しただけ)

    <html>
    <head>
        <!-- Styles -->
        <style>
            #chartdiv {
                width: 100%;
                height: 600px;
            }
        </style>
    </head>
    <body>
        <!-- HTML -->
        <div id="chartdiv"></div>
    
        <!-- Resources -->
        <script src="https://cdn.amcharts.com/lib/4/core.js"></script>
        <script src="https://cdn.amcharts.com/lib/4/charts.js"></script>
        <script src="https://cdn.amcharts.com/lib/4/plugins/wordCloud.js"></script>
        <script src="https://cdn.amcharts.com/lib/4/themes/animated.js"></script>
    
        <!-- Chart code -->
        <script>
            am4core.ready(function() {
    
                // Themes begin
                am4core.useTheme(am4themes_animated);
                // Themes end
    
                const chart = am4core.create("chartdiv", am4plugins_wordCloud.WordCloud);
                chart.fontFamily = "Courier New";
                const series = chart.series.push(new am4plugins_wordCloud.WordCloudSeries());
                series.randomness = 0.1;
                series.rotationThreshold = 0.5;
    
                series.data = [
                    {
                        "tag": "javascript",
                        "count": "1765836"
                    }, {
                        "tag": "java",
                        "count": "1517355"
                    }, {
                        "tag": "c#",
                        "count": "1287629"
                    }, {
                        "tag": "validation",
                        "count": "55531"
                    }
                ];
                // 長かったので省略
    
                series.dataFields.word = "tag";
                series.dataFields.value = "count";
    
                series.heatRules.push({
                    "target": series.labels.template,
                    "property": "fill",
                    "min": am4core.color("#0000CC"),
                    "max": am4core.color("#CC00CC"),
                    "dataField": "value"
                });
    
                series.labels.template.url = "https://stackoverflow.com/questions/tagged/{word}";
                series.labels.template.urlTarget = "_blank";
                series.labels.template.tooltipText = "{word}: {value}";
    
                const hoverState = series.labels.template.states.create("hover");
                hoverState.properties.fill = am4core.color("#FF0000");
    
                const subtitle = chart.titles.create();
                subtitle.text = "(click to open)";
    
                const title = chart.titles.create();
                title.text = "Most Popular Tags @ StackOverflow";
                title.fontSize = 20;
                title.fontWeight = "800";
    
            }); // end am4core.ready()
        </script>
    </body>
    </html>
  • VSCode の Live Server プラグインでローカルWebサーバ上で実行する
  • ブラウザで確認する

無事動いてますね。
次は、使い方を確認していこうと思います。

使い方

ドキュメントの分量も少ないので読めばわかると思いますが、一応試した内容を解説します。

チャートの作成

スクリプトを読み込み

<script src="https://cdn.amcharts.com/lib/4/core.js"></script>
<script src="https://cdn.amcharts.com/lib/4/charts.js"></script>
<script src="https://cdn.amcharts.com/lib/4/plugins/wordCloud.js"></script>
<script src="https://cdn.amcharts.com/lib/4/themes/animated.js"></script>

チャートを描画するためのdivを作成して

<div id="chartdiv"></div>

Chartを作成します。

am4core.ready(function() {
    // ここは`am4core`が利用可能になったら実行されるエリア
    // amChartsに関する全ての処理はここに記載する
    const chart = am4core.create("chartdiv", am4plugins_wordCloud.WordCloud);
});

データ

チャートに表示するデータを保持するシリーズ(Series)を作成します。

const series = chart.series.push(new am4plugins_wordCloud.WordCloudSeries());

シリーズにデータを投入する方法は二つあるようです。

Plane Text

事前に構造化しなくても、直接テキストをデータとして指定することもできます。
DEMOコードを少し修正するだけです。

series.text = "タグ・クラウド (tag cloud、 ワードクラウド 、またはビジュアルデザインの重み付きリスト )はタグの視覚的記述を指す。テキストデータの斬新な視覚表現であり、通常はWebサイトでキーワードメタデータ(タグ)を描写したり、自由形式のテキストを視覚化するが、通常タグは単一の単語であり、各タグの重要性はフォントサイズまたは色で示される[2]。 この形式は、最も重要な用語をすばやく認識して、その相対的な重要度を判断するのに役立つが Webサイトのナビゲーション支援として使用される場合、用語はタグに関連付けられたアイテムにハイパーリンクされる。";

助詞などのストップワードが強調されているのもいまいちですが、minWordLengthexcludeWordsなどの設定である程度は是正することができます。

series.minWordLength = 2;
series.excludeWords = ["される", "であり", "この", "その"];

とはいえ、日本語だと形態素解析が全然ダメなので、Plane textを使うのはほぼありえないかな、と思います。
(ちなみに、英語だとスペースできちんと単語が区切られるので良い感じでした)

構造化データ

構造化データ(ワード単位の重み付けされたデータ)を利用するパターンは上記のDEMOコードそのままです。

series.data = [
    {
        "tag": "javascript",
        "count": "1765836"
    }, {
        "tag": "java",
        "count": "1517355"
    }
];
series.dataFields.word = "tag";
series.dataFields.value = "count";

dataFieldのwordvalueにdataのキーを指定してあげるだけで動いてくれます。
これであれば、日本語であっても事前にkuromojiなどの形態素解析エンジンを使って単語分割できるので、機能的な制限もありません。

分散具合

分散具合を調整するパラメータは二つあるようです。

  • series.accuracy
    • 単語の表示場所探しの正確さを0以上の数値で指定する
    • 数値が大きいほど密集する
    • 数値が小さいほど分散するし、スクリプト実行時間が延びる
  • series.randomness
    • どれぐらい単語が分散すべきか
    • 0はランダム性がないことを示し、中心に全ての単語が配置される
    • 1は完全なランダムで、外側まで含めて全体に均一に分散する

文字の角度

  • series.angles
    • 文字の角度を指定する
    • series.angles = [0, 0, 90]; だと、0度(水平)と90度(垂直)だけど、0が二回指定されてるので2/3が水平、1/3が垂直に表示される
    • series.angles = [0, 45, 90]; だと、以下のようになる
  • series.rotationThreshold
    • 0〜1までの値を指定する
    • 単語の長さが、chartの高さ × rotationThreshold を超えたら回転しない(水平表示)となる
  • series.labelsContainer.rotation
    • 全体を一定の角度回転する
    • 例えば、series.labelsContainer.rotation = -45;とすると、一旦anglesなどの設定で描画された状態から-45度回転する形になる

ラベル・ツールチップ・リンクURL

  • series.labels.template.text
    • ラベルの値を指定する。デフォルトは{word}
    • series.labels.template.text = "{word}({value})";だと以下のようになる
  • series.labels.template.tooltipText
    • ツールチップの値を指定する。ラベルと同様の形式で指定できる
  • series.labels.template.url
    • ラベルをクリックした時に表示するリンクURLを指定する
    • 例えば以下の設定だとクリック時に、新しいタブでgoogle検索結果を表示する
      series.labels.template.url = "https://www.google.com/search?q={word}";
      series.labels.template.urlTarget = "_blank";

イベント

ラベルのイベント

ラベルをクリックした時のイベントを拾って、クリップボードにコピーする例です。

series.labels.template.events.on("hit", (ev) => {
    if (navigator.clipboard) {
        navigator.clipboard.writeText(ev.target.dataItem.word);
    }
});

hit以外にもたくさんのイベントがありますので用途に応じて使えそうです。

シリーズのイベント

単語の整理開始、整理終了などのイベントを拾って、コンソールログを出力する例です。

series.events.on("arrangestarted", function(ev) {
    console.log("arrange started.");
});
series.events.on("arrangeended", function(ev) {
    console.log("arrange finished.");
});

最後に

ある程度、利用ケースを想定して動作確認してみましたが、機能不足は感じませんでした。
強いて言うなら、日本語の形態素解析は事前に行う必要がある、ぐらいでしょうか。
そこも、事前に別のライブラリで処理すればいいだけなので、特に不都合はありません。
使い方も簡単なので、タグクラウドをブラウザ側で表示したいようなケースでは、重宝しそうです。