LoginSignup
10
13

More than 5 years have passed since last update.

Node.js+D3.js+Socket.io+StreamingAPIによる実況可視化ツールを作る

Last updated at Posted at 2017-02-23

目的

 前回Twitter実況の可視化を行いたいと展望で述べました。
 実況内容を集計するところまではできましたが、集計結果の内容は膨大であり、内容も変動するため、実況から「今何がホットなのか」を提示するという目的は未達だったというのが経緯です。
 これについて進展がありましたので報告します。

結論

 Twitter野球実況の形態素解析結果を可視化(ワードクラウド化)しました。
 これにより以下の恩恵が得られました。

  1. 人間が一目で「今何がホットなのか」を判別できる
  2. 各球団の色でワードクラウドを着色していることにより、「どの球団で何がホットなのか」も知ることができる

内容

コード

 d3-cloudによる描画処理部(フロントエンド)のみ記載します。

client.js
function wordCloud(selector) {
  const W = 800; // 描画範囲サイズwidth
  const H = 500; // 描画範囲サイズheight
  const P = 4;   // ワードクラウド文字間のpadding

  // wordCloud関数のコンストラクタ(描画初期設定)
  var svg = d3.select(selector).append("svg")
    .attr("width", W)
    .attr("height", H)
    .append("g")
    .attr("transform", "translate("+W/2+","+H/2+")");


  // 描画(draw)関数
  function draw(words) {
    var cloud = svg.selectAll("g text")
                   .data(words, function(d) { return d.word; })

    // ワード出現時&ワード存在時の処理
    cloud.enter()
      .append("text")
      .style("fill", function(d) { return d.color; })
      .attr("text-anchor", "middle")
      .attr('font-size', 1)
    .merge(cloud)
      .transition()
        .duration(600)
        .style("font-size", function(d) { return d.size + "px"; })
        .attr("transform", function(d) {
          return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
        })
        .style("fill-opacity", 1)
        .text(function(d) { return d.word; });

    // ワード消去時処理
    cloud.exit()
      .transition()
        .duration(200)
        .style('fill-opacity', 1e-6)
        .attr('font-size', 1)
        .remove();
  }


  // 再描画時に呼び出す関数
  return {
    update: function(words) {
      var random = d3.randomIrwinHall(2);

      d3.layout.cloud().size([W, H])
        .words(words)
        .padding(P)
        .rotate(function() {
          //return Math.round(1-random())*90; //文字をランダム回転させたい場合
          return 0; //回転させない場合
        })
        .text(function(d) { return d.word; })
        .fontSize(function(d) { return d.size; })
        .on("end", draw)
        .start();
    }
  }

}

// 与えられたデータ([{},{},{},...])を描画可能なオブジェクトに成形する
function getWords(data) {
  var countMax   = d3.max(data, function(d){return d.count});
  var sizeScale  = d3.scaleLog().domain([1, countMax]).range([10, 50]); //ログスケール
  //var sizeScale  = d3.scaleLinear().domain([0, countMax]).range([10, 100]); //リニアスケール
  // 各チームの色を指定する関数
  var colorScale = function(t){
    switch(t) {
      case "baystars":  return d3.color("dodgerblue");
      case "giants":    return d3.color("orange");
      case "tigers":    return d3.color("yellow");
      case "swallows":  return d3.color("limegreen");
      case "dragons":   return d3.color("darkblue");
      case "carp":      return d3.color("red");
      case "hawks":     return d3.color("gold");
      case "lions":     return d3.color("aqua");
      case "buffaloes": return d3.color("darkgoldenrod");
      case "eagles":    return d3.color("firebrick");
      case "fighters":  return d3.color("steelblue");
      case "marines":   return d3.color("black");
      default:          return d3.color("gray");
    }
  };
  return data.map( function(d) {
    return {
      word:  d.word, 
      size:  sizeScale(d.count),
      color: colorScale(d.team)
    }
  })
}

function showNewWords(vis, data) {
  vis.update(getWords(data))
}

// -------------------------
// ワードクラウドのインスタンス生成
var myWordCloud = wordCloud('#main');

// Socket.io経由でサーバのレスポンスの度に以下実行
var ioSocket = io.connect("http://192.168.33.10:8080");
ioSocket.on('result_analyze', function(data) {
  showNewWords(myWordCloud, data);
});

MeCabの新語辞書導入

 前回、野球選手名が適切に分解されない問題があり、今回はMeCabの新語辞書"mecab-ipadic-NEologd"を導入しています。

 著名な選手ですと、明らかに適切に分解されるようになりましたが、新戦力(ルーキーや新外国人など)ですと流石に分解されません。

メモリ不足によるインストールエラー

 ちなみに、辞書インストール時に以下ログが出てメモリ不足となり、インストールが中止される不具合がありました。

terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Aborted (core dumped)

 調べると全く同じ現象について書かれた以下の記事があり、一発でした。

以下のようにvb.customizeを追記した(config.vm.providerも)。

Vagrant.configure(2) do |config|
    config.vm.box = "ubuntu1404"
    config.vm.provider :virtualbox do |vb|
        vb.customize ["modifyvm", :id, "--memory", 1024]
    end
end

空想ブログ: "Ubuntu14.04でmecabの辞書にWikipediaとはてな単語を追加"より抜粋

展望

  • D3.js周りの設計はこの記事までとします。今はD3.jsの利用方法の習得よりも、JavaScriptの基礎知識の習得を優先したいためです。「ドキュメントやFAQがほぼ英語(Qiita記事は230件しかない)」、「多機能すぎてネイティブJavaScriptとは別物」である点が習得を難しくさせていると感じます。
  • 「野球選手用名解析用ユーザ辞書」の生成と導入を検討。→導入しました
  • 「12球団ごとの毎秒ツイート数」をサーバサイドで計算させているので、それをDOMに反映する。
  • 阪神タイガースの文字色(yellow)が背景と同化してしまっている。。
  • 球団名などはワードクラウド表示させない。例えば、ワードクラウド上の赤は広島カープのHOTワードを表しているが、赤で「広島」や「カープ」が大きく表示されるのは当たり前。既知の情報は除外する方が、情報として有用。

所見

  • D3.jsが異常に難しい。コピーしたコードを実行するだけでかなりの時間を浪費してしまいました(主にD3のバージョンがv3→v4間で大幅にAPIが変わった点に苦しめられました。既存のドキュメントで対応できない)。
  • 今回の目的は「情報を分かりやすく伝える」ことであり、「そのためのツールを使う」ことに時間がかかりすぎる(手段が目的と化す)のは避けたい。

参考

1. GitHub: "D3 API Reference"
2. GitHub: "d3-cloud API Reference"
3. GUNMA GIS GEEK: "【D3.js】「全ツイート履歴」からWord cloudを作ってみた。"
4. d3.jsな日々: "スケーリング:domainとrange"
5. About Blocks: "Animated d3 word cloud"

10
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
13