follow、被followの比率を円グラフで表示

Twitter APIで戻ってくるXMLをパーズして、それから何がしかの情報を取ってくる場合、もしかしてSAXを使うべきじゃないだろうかと思った。XPathとかって、ある階層のノードをまとめて引っ張ってきてイテレートするにはいいけど、例えばuser/nameとuser/idと別々にイテレートするとか、なんか変。そもそもuserのプロパティに欠けがあったら対応が取れなくなるし。といって、userごとにまた処理を分けるのも、めんどい。

単にXMLで書かれたデータをオブジェクトにマップするだけなら、頭から順にタグのイベントを発生させてくれるだけのSAXのほうがシンプル。

で、SAXで書いてみた。実行結果はHTMLで吐いて、円グラフをGoogle Chart APIで描く。円グラフはfollow数と、被follow数の比率を示している。follow、被followの割合って、その人がTwitter上でどういう存在かを示す1つの指標になると思って可視化してみたかった。ボット系が極端に被follow率が高いとか、有名な人が被follow率が高めというのは予想通りだったけど、両者が均等という人が多いのが意外だった。みんなfollow返ししてるのね。

HTML生成にnokogiri使えよって感じ。そもそも結果全体をtableで囲むとかもないわ。ほんとは流行のcanvasを使ってみたかったけど、Googleのサービスがやたら反応が速いし手軽だったので、これでいいじゃんって感じ。

require 'open-uri'
require 'nokogiri'

class User
  attr_accessor :username, :followers, :friends, :image_url
  attr_reader :ratio

  def initialize
    @username = ""
    @followers = 0
    @friends = 0
    @image_url = ""
    @ratio = 0
  end

  def calc_ratio
    unless @friends == 0
      @ratio = @followers.to_f / @friends.to_f
    else
      @ratio = 0 # instead of infinity
    end
  end
end

class Piechart
  @@api = "http://chart.apis.google.com/chart?cht=p3&chd=t:"

  def initialize(a, b)
    total = a + b
    @a = sprintf("%.2f", (100 * (a.to_f / total)))
    @b = sprintf("%.2f", (100 * (b.to_f / total)))
  end

  def create_query
    query = @@api+@a+","+@b+"&chs=250x100&chl=Following|Followed"
    query += "&chco=ff0000|0000ff" # color
    query = "<img src=\"" + query + "\"/>"
  end
end

class MyDocument < Nokogiri::XML::SAX::Document
  def initialize
    @users = []
    @str = ""
    @u = User.new
  end

  def start_element(name, attributes = [])
    if name == "user"
      @u = User.new
    end
  end

  def characters(str)
    @str = str
  end

  def end_element(name, attributes = [])
    case name
    when "name" then  @u.username = @str
    when "profile_image_url" then @u.image_url = @str
    when "followers_count" then @u.followers = @str.to_i
    when "friends_count" then @u.friends = @str.to_i
    when "user"  then
      @u.calc_ratio
      @users << @u
    end
  end

  def end_document
    print "<table border=\"1\">\n"
    @users.each do |u|
      #        print u.username, ":", u.friends, "/",  u.followers, "--> "
      #        printf("%.2f\n",  u.ratio)
      pie = Piechart.new(u.friends, u.followers)
      print "<tr><td><img src=\"", u.image_url, "\"></td>"
      print "<td>", u.username, "</td><td>", pie.create_query, "(", u.friends, ") </td></tr>\n"
    end
    print "</table>\n"
  end

end

# Create a new parser
parser = Nokogiri::XML::SAX::Parser.new(MyDocument.new)

# Feed the parser some XML
#parser.parse(File.read(ARGV[0]))

option = "?page=1"
username = ARGV.shift

parser.parse(open("http://twitter.com/statuses/friends/"+username+".xml"+option))